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
170func TestPointableInterface(t *testing.T) {
171	p := &pointableImpl{"hello"}
172
173	result, _, err := GetForToken(p, "some")
174	assert.NoError(t, err)
175	assert.Equal(t, p.a, result)
176
177	result, _, err = GetForToken(p, "something")
178	assert.Error(t, err)
179	assert.Nil(t, result)
180}
181
182func TestGetNode(t *testing.T) {
183
184	in := `/obj`
185
186	p, err := New(in)
187	assert.NoError(t, err)
188	result, _, err := p.Get(testDocumentJSON)
189	assert.NoError(t, err)
190	assert.Len(t, result, TestNodeObjNBItems)
191
192	result, _, err = p.Get(aliasedMap(testDocumentJSON.(map[string]interface{})))
193	assert.NoError(t, err)
194	assert.Len(t, result, TestNodeObjNBItems)
195
196	result, _, err = p.Get(testStructJSONDoc)
197	assert.NoError(t, err)
198	assert.Equal(t, testStructJSONDoc.Obj, result)
199
200	result, _, err = p.Get(testStructJSONPtr)
201	assert.NoError(t, err)
202	assert.Equal(t, testStructJSONDoc.Obj, result)
203}
204
205func TestArray(t *testing.T) {
206
207	ins := []string{`/foo/0`, `/foo/0`, `/foo/1`}
208	outs := []string{"bar", "bar", "baz"}
209
210	for i := range ins {
211		p, err := New(ins[i])
212		assert.NoError(t, err)
213
214		result, _, err := p.Get(testStructJSONDoc)
215		assert.NoError(t, err)
216		assert.Equal(t, outs[i], result)
217
218		result, _, err = p.Get(testStructJSONPtr)
219		assert.NoError(t, err)
220		assert.Equal(t, outs[i], result)
221
222		result, _, err = p.Get(testDocumentJSON)
223		assert.NoError(t, err)
224		assert.Equal(t, outs[i], result)
225	}
226}
227
228func TestOtherThings(t *testing.T) {
229	_, err := New("abc")
230	assert.Error(t, err)
231
232	p, err := New("")
233	assert.NoError(t, err)
234	assert.Equal(t, "", p.String())
235
236	p, err = New("/obj/a")
237	assert.Equal(t, "/obj/a", p.String())
238
239	s := Escape("m~n")
240	assert.Equal(t, "m~0n", s)
241	s = Escape("m/n")
242	assert.Equal(t, "m~1n", s)
243
244	p, err = New("/foo/3")
245	assert.NoError(t, err)
246	_, _, err = p.Get(testDocumentJSON)
247	assert.Error(t, err)
248
249	p, err = New("/foo/a")
250	assert.NoError(t, err)
251	_, _, err = p.Get(testDocumentJSON)
252	assert.Error(t, err)
253
254	p, err = New("/notthere")
255	assert.NoError(t, err)
256	_, _, err = p.Get(testDocumentJSON)
257	assert.Error(t, err)
258
259	p, err = New("/invalid")
260	assert.NoError(t, err)
261	_, _, err = p.Get(1234)
262	assert.Error(t, err)
263
264	p, err = New("/foo/1")
265	assert.NoError(t, err)
266	expected := "hello"
267	bbb := testDocumentJSON.(map[string]interface{})["foo"]
268	bbb.([]interface{})[1] = "hello"
269
270	v, _, err := p.Get(testDocumentJSON)
271	assert.NoError(t, err)
272	assert.Equal(t, expected, v)
273
274	esc := Escape("a/")
275	assert.Equal(t, "a~1", esc)
276	unesc := Unescape(esc)
277	assert.Equal(t, "a/", unesc)
278
279	unesc = Unescape("~01")
280	assert.Equal(t, "~1", unesc)
281	assert.Equal(t, "~0~1", Escape("~/"))
282	assert.Equal(t, "~/", Unescape("~0~1"))
283}
284
285func TestObject(t *testing.T) {
286
287	ins := []string{`/obj/a`, `/obj/b`, `/obj/c/0`, `/obj/c/1`, `/obj/c/1`, `/obj/d/1/f/0`}
288	outs := []float64{1, 2, 3, 4, 4, 50}
289
290	for i := range ins {
291
292		p, err := New(ins[i])
293		assert.NoError(t, err)
294
295		result, _, err := p.Get(testDocumentJSON)
296		assert.NoError(t, err)
297		assert.Equal(t, outs[i], result)
298
299		result, _, err = p.Get(testStructJSONDoc)
300		assert.NoError(t, err)
301		assert.EqualValues(t, outs[i], result)
302
303		result, _, err = p.Get(testStructJSONPtr)
304		assert.NoError(t, err)
305		assert.EqualValues(t, outs[i], result)
306	}
307}
308
309type setJsonDocEle struct {
310	B int `json:"b"`
311	C int `json:"c"`
312}
313type setJsonDoc struct {
314	A []struct {
315		B int `json:"b"`
316		C int `json:"c"`
317	} `json:"a"`
318	D int `json:"d"`
319}
320
321type settableDoc struct {
322	Coll settableColl
323	Int  settableInt
324}
325
326func (s settableDoc) MarshalJSON() ([]byte, error) {
327	var res struct {
328		A settableColl `json:"a"`
329		D settableInt  `json:"d"`
330	}
331	res.A = s.Coll
332	res.D = s.Int
333	return json.Marshal(res)
334}
335func (s *settableDoc) UnmarshalJSON(data []byte) error {
336	var res struct {
337		A settableColl `json:"a"`
338		D settableInt  `json:"d"`
339	}
340
341	if err := json.Unmarshal(data, &res); err != nil {
342		return err
343	}
344	s.Coll = res.A
345	s.Int = res.D
346	return nil
347}
348
349// JSONLookup implements an interface to customize json pointer lookup
350func (s settableDoc) JSONLookup(token string) (interface{}, error) {
351	switch token {
352	case "a":
353		return &s.Coll, nil
354	case "d":
355		return &s.Int, nil
356	default:
357		return nil, fmt.Errorf("%s is not a known field", token)
358	}
359}
360
361// JSONLookup implements an interface to customize json pointer lookup
362func (s *settableDoc) JSONSet(token string, data interface{}) error {
363	switch token {
364	case "a":
365		switch dt := data.(type) {
366		case settableColl:
367			s.Coll = dt
368			return nil
369		case *settableColl:
370			if dt != nil {
371				s.Coll = *dt
372			} else {
373				s.Coll = settableColl{}
374			}
375			return nil
376		case []settableCollItem:
377			s.Coll.Items = dt
378			return nil
379		}
380	case "d":
381		switch dt := data.(type) {
382		case settableInt:
383			s.Int = dt
384			return nil
385		case int:
386			s.Int.Value = dt
387			return nil
388		case int8:
389			s.Int.Value = int(dt)
390			return nil
391		case int16:
392			s.Int.Value = int(dt)
393			return nil
394		case int32:
395			s.Int.Value = int(dt)
396			return nil
397		case int64:
398			s.Int.Value = int(dt)
399			return nil
400		default:
401			return fmt.Errorf("invalid type %T for %s", data, token)
402		}
403	}
404	return fmt.Errorf("%s is not a known field", token)
405}
406
407type settableColl struct {
408	Items []settableCollItem
409}
410
411func (s settableColl) MarshalJSON() ([]byte, error) {
412	return json.Marshal(s.Items)
413}
414func (s *settableColl) UnmarshalJSON(data []byte) error {
415	return json.Unmarshal(data, &s.Items)
416}
417
418// JSONLookup implements an interface to customize json pointer lookup
419func (s settableColl) JSONLookup(token string) (interface{}, error) {
420	if tok, err := strconv.Atoi(token); err == nil {
421		return &s.Items[tok], nil
422	}
423	return nil, fmt.Errorf("%s is not a valid index", token)
424}
425
426// JSONLookup implements an interface to customize json pointer lookup
427func (s *settableColl) JSONSet(token string, data interface{}) error {
428	if _, err := strconv.Atoi(token); err == nil {
429		_, err := SetForToken(s.Items, token, data)
430		return err
431	}
432	return fmt.Errorf("%s is not a valid index", token)
433}
434
435type settableCollItem struct {
436	B int `json:"b"`
437	C int `json:"c"`
438}
439
440type settableInt struct {
441	Value int
442}
443
444func (s settableInt) MarshalJSON() ([]byte, error) {
445	return json.Marshal(s.Value)
446}
447func (s *settableInt) UnmarshalJSON(data []byte) error {
448	return json.Unmarshal(data, &s.Value)
449}
450
451func TestSetNode(t *testing.T) {
452
453	jsonText := `{"a":[{"b": 1, "c": 2}], "d": 3}`
454
455	var jsonDocument interface{}
456	if assert.NoError(t, json.Unmarshal([]byte(jsonText), &jsonDocument)) {
457		in := "/a/0/c"
458		p, err := New(in)
459		if assert.NoError(t, err) {
460
461			_, err = p.Set(jsonDocument, 999)
462			assert.NoError(t, err)
463
464			firstNode := jsonDocument.(map[string]interface{})
465			assert.Len(t, firstNode, 2)
466
467			sliceNode := firstNode["a"].([]interface{})
468			assert.Len(t, sliceNode, 1)
469
470			changedNode := sliceNode[0].(map[string]interface{})
471			chNodeVI := changedNode["c"]
472			if assert.IsType(t, 0, chNodeVI) {
473				changedNodeValue := chNodeVI.(int)
474				if assert.Equal(t, 999, changedNodeValue) {
475					assert.Len(t, sliceNode, 1)
476				}
477			}
478		}
479
480		v, err := New("/a/0")
481		if assert.NoError(t, err) {
482			_, err = v.Set(jsonDocument, map[string]interface{}{"b": 3, "c": 8})
483			if assert.NoError(t, err) {
484				firstNode := jsonDocument.(map[string]interface{})
485				assert.Len(t, firstNode, 2)
486
487				sliceNode := firstNode["a"].([]interface{})
488				assert.Len(t, sliceNode, 1)
489				changedNode := sliceNode[0].(map[string]interface{})
490				assert.Equal(t, 3, changedNode["b"])
491				assert.Equal(t, 8, changedNode["c"])
492			}
493		}
494	}
495
496	var structDoc setJsonDoc
497	if assert.NoError(t, json.Unmarshal([]byte(jsonText), &structDoc)) {
498		g, err := New("/a")
499		if assert.NoError(t, err) {
500			_, err = g.Set(&structDoc, []struct {
501				B int `json:"b"`
502				C int `json:"c"`
503			}{{B: 4, C: 7}})
504
505			if assert.NoError(t, err) {
506				assert.Len(t, structDoc.A, 1)
507				changedNode := structDoc.A[0]
508				assert.Equal(t, 4, changedNode.B)
509				assert.Equal(t, 7, changedNode.C)
510			}
511		}
512
513		v, err := New("/a/0")
514		if assert.NoError(t, err) {
515			_, err = v.Set(structDoc, struct {
516				B int `json:"b"`
517				C int `json:"c"`
518			}{B: 3, C: 8})
519
520			if assert.NoError(t, err) {
521				assert.Len(t, structDoc.A, 1)
522				changedNode := structDoc.A[0]
523				assert.Equal(t, 3, changedNode.B)
524				assert.Equal(t, 8, changedNode.C)
525			}
526		}
527
528		p, err := New("/a/0/c")
529		if assert.NoError(t, err) {
530			_, err = p.Set(&structDoc, 999)
531			assert.NoError(t, err)
532			if assert.Len(t, structDoc.A, 1) {
533				assert.Equal(t, 999, structDoc.A[0].C)
534			}
535		}
536	}
537
538	var setDoc settableDoc
539	if assert.NoError(t, json.Unmarshal([]byte(jsonText), &setDoc)) {
540		g, err := New("/a")
541		if assert.NoError(t, err) {
542			_, err = g.Set(&setDoc, []settableCollItem{{B: 4, C: 7}})
543
544			if assert.NoError(t, err) {
545				assert.Len(t, setDoc.Coll.Items, 1)
546				changedNode := setDoc.Coll.Items[0]
547				assert.Equal(t, 4, changedNode.B)
548				assert.Equal(t, 7, changedNode.C)
549			}
550		}
551
552		v, err := New("/a/0")
553		if assert.NoError(t, err) {
554			_, err = v.Set(setDoc, settableCollItem{B: 3, C: 8})
555
556			if assert.NoError(t, err) {
557				assert.Len(t, setDoc.Coll.Items, 1)
558				changedNode := setDoc.Coll.Items[0]
559				assert.Equal(t, 3, changedNode.B)
560				assert.Equal(t, 8, changedNode.C)
561			}
562		}
563
564		p, err := New("/a/0/c")
565		if assert.NoError(t, err) {
566			_, err = p.Set(setDoc, 999)
567			assert.NoError(t, err)
568			if assert.Len(t, setDoc.Coll.Items, 1) {
569				assert.Equal(t, 999, setDoc.Coll.Items[0].C)
570			}
571		}
572	}
573}
574