1/*
2Copyright 2018 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package fieldpath
18
19import (
20	"errors"
21	"fmt"
22	"io"
23	"strconv"
24	"strings"
25
26	jsoniter "github.com/json-iterator/go"
27	"sigs.k8s.io/structured-merge-diff/v4/value"
28)
29
30var ErrUnknownPathElementType = errors.New("unknown path element type")
31
32const (
33	// Field indicates that the content of this path element is a field's name
34	peField = "f"
35
36	// Value indicates that the content of this path element is a field's value
37	peValue = "v"
38
39	// Index indicates that the content of this path element is an index in an array
40	peIndex = "i"
41
42	// Key indicates that the content of this path element is a key value map
43	peKey = "k"
44
45	// Separator separates the type of a path element from the contents
46	peSeparator = ":"
47)
48
49var (
50	peFieldSepBytes = []byte(peField + peSeparator)
51	peValueSepBytes = []byte(peValue + peSeparator)
52	peIndexSepBytes = []byte(peIndex + peSeparator)
53	peKeySepBytes   = []byte(peKey + peSeparator)
54	peSepBytes      = []byte(peSeparator)
55)
56
57// DeserializePathElement parses a serialized path element
58func DeserializePathElement(s string) (PathElement, error) {
59	b := []byte(s)
60	if len(b) < 2 {
61		return PathElement{}, errors.New("key must be 2 characters long:")
62	}
63	typeSep, b := b[:2], b[2:]
64	if typeSep[1] != peSepBytes[0] {
65		return PathElement{}, fmt.Errorf("missing colon: %v", s)
66	}
67	switch typeSep[0] {
68	case peFieldSepBytes[0]:
69		// Slice s rather than convert b, to save on
70		// allocations.
71		str := s[2:]
72		return PathElement{
73			FieldName: &str,
74		}, nil
75	case peValueSepBytes[0]:
76		iter := readPool.BorrowIterator(b)
77		defer readPool.ReturnIterator(iter)
78		v, err := value.ReadJSONIter(iter)
79		if err != nil {
80			return PathElement{}, err
81		}
82		return PathElement{Value: &v}, nil
83	case peKeySepBytes[0]:
84		iter := readPool.BorrowIterator(b)
85		defer readPool.ReturnIterator(iter)
86		fields := value.FieldList{}
87
88		iter.ReadObjectCB(func(iter *jsoniter.Iterator, key string) bool {
89			v, err := value.ReadJSONIter(iter)
90			if err != nil {
91				iter.Error = err
92				return false
93			}
94			fields = append(fields, value.Field{Name: key, Value: v})
95			return true
96		})
97		fields.Sort()
98		return PathElement{Key: &fields}, iter.Error
99	case peIndexSepBytes[0]:
100		i, err := strconv.Atoi(s[2:])
101		if err != nil {
102			return PathElement{}, err
103		}
104		return PathElement{
105			Index: &i,
106		}, nil
107	default:
108		return PathElement{}, ErrUnknownPathElementType
109	}
110}
111
112var (
113	readPool  = jsoniter.NewIterator(jsoniter.ConfigCompatibleWithStandardLibrary).Pool()
114	writePool = jsoniter.NewStream(jsoniter.ConfigCompatibleWithStandardLibrary, nil, 1024).Pool()
115)
116
117// SerializePathElement serializes a path element
118func SerializePathElement(pe PathElement) (string, error) {
119	buf := strings.Builder{}
120	err := serializePathElementToWriter(&buf, pe)
121	return buf.String(), err
122}
123
124func serializePathElementToWriter(w io.Writer, pe PathElement) error {
125	stream := writePool.BorrowStream(w)
126	defer writePool.ReturnStream(stream)
127	switch {
128	case pe.FieldName != nil:
129		if _, err := stream.Write(peFieldSepBytes); err != nil {
130			return err
131		}
132		stream.WriteRaw(*pe.FieldName)
133	case pe.Key != nil:
134		if _, err := stream.Write(peKeySepBytes); err != nil {
135			return err
136		}
137		stream.WriteObjectStart()
138
139		for i, field := range *pe.Key {
140			if i > 0 {
141				stream.WriteMore()
142			}
143			stream.WriteObjectField(field.Name)
144			value.WriteJSONStream(field.Value, stream)
145		}
146		stream.WriteObjectEnd()
147	case pe.Value != nil:
148		if _, err := stream.Write(peValueSepBytes); err != nil {
149			return err
150		}
151		value.WriteJSONStream(*pe.Value, stream)
152	case pe.Index != nil:
153		if _, err := stream.Write(peIndexSepBytes); err != nil {
154			return err
155		}
156		stream.WriteInt(*pe.Index)
157	default:
158		return errors.New("invalid PathElement")
159	}
160	b := stream.Buffer()
161	err := stream.Flush()
162	// Help jsoniter manage its buffers--without this, the next
163	// use of the stream is likely to require an allocation. Look
164	// at the jsoniter stream code to understand why. They were probably
165	// optimizing for folks using the buffer directly.
166	stream.SetBuffer(b[:0])
167	return err
168}
169