mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 05:40:42 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			169 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			169 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| Copyright 2018 The Kubernetes Authors.
 | |
| 
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
|     http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| */
 | |
| 
 | |
| package fieldpath
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 
 | |
| 	jsoniter "github.com/json-iterator/go"
 | |
| 	"sigs.k8s.io/structured-merge-diff/v4/value"
 | |
| )
 | |
| 
 | |
| var ErrUnknownPathElementType = errors.New("unknown path element type")
 | |
| 
 | |
| const (
 | |
| 	// Field indicates that the content of this path element is a field's name
 | |
| 	peField = "f"
 | |
| 
 | |
| 	// Value indicates that the content of this path element is a field's value
 | |
| 	peValue = "v"
 | |
| 
 | |
| 	// Index indicates that the content of this path element is an index in an array
 | |
| 	peIndex = "i"
 | |
| 
 | |
| 	// Key indicates that the content of this path element is a key value map
 | |
| 	peKey = "k"
 | |
| 
 | |
| 	// Separator separates the type of a path element from the contents
 | |
| 	peSeparator = ":"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	peFieldSepBytes = []byte(peField + peSeparator)
 | |
| 	peValueSepBytes = []byte(peValue + peSeparator)
 | |
| 	peIndexSepBytes = []byte(peIndex + peSeparator)
 | |
| 	peKeySepBytes   = []byte(peKey + peSeparator)
 | |
| 	peSepBytes      = []byte(peSeparator)
 | |
| )
 | |
| 
 | |
| // DeserializePathElement parses a serialized path element
 | |
| func DeserializePathElement(s string) (PathElement, error) {
 | |
| 	b := []byte(s)
 | |
| 	if len(b) < 2 {
 | |
| 		return PathElement{}, errors.New("key must be 2 characters long:")
 | |
| 	}
 | |
| 	typeSep, b := b[:2], b[2:]
 | |
| 	if typeSep[1] != peSepBytes[0] {
 | |
| 		return PathElement{}, fmt.Errorf("missing colon: %v", s)
 | |
| 	}
 | |
| 	switch typeSep[0] {
 | |
| 	case peFieldSepBytes[0]:
 | |
| 		// Slice s rather than convert b, to save on
 | |
| 		// allocations.
 | |
| 		str := s[2:]
 | |
| 		return PathElement{
 | |
| 			FieldName: &str,
 | |
| 		}, nil
 | |
| 	case peValueSepBytes[0]:
 | |
| 		iter := readPool.BorrowIterator(b)
 | |
| 		defer readPool.ReturnIterator(iter)
 | |
| 		v, err := value.ReadJSONIter(iter)
 | |
| 		if err != nil {
 | |
| 			return PathElement{}, err
 | |
| 		}
 | |
| 		return PathElement{Value: &v}, nil
 | |
| 	case peKeySepBytes[0]:
 | |
| 		iter := readPool.BorrowIterator(b)
 | |
| 		defer readPool.ReturnIterator(iter)
 | |
| 		fields := value.FieldList{}
 | |
| 
 | |
| 		iter.ReadObjectCB(func(iter *jsoniter.Iterator, key string) bool {
 | |
| 			v, err := value.ReadJSONIter(iter)
 | |
| 			if err != nil {
 | |
| 				iter.Error = err
 | |
| 				return false
 | |
| 			}
 | |
| 			fields = append(fields, value.Field{Name: key, Value: v})
 | |
| 			return true
 | |
| 		})
 | |
| 		fields.Sort()
 | |
| 		return PathElement{Key: &fields}, iter.Error
 | |
| 	case peIndexSepBytes[0]:
 | |
| 		i, err := strconv.Atoi(s[2:])
 | |
| 		if err != nil {
 | |
| 			return PathElement{}, err
 | |
| 		}
 | |
| 		return PathElement{
 | |
| 			Index: &i,
 | |
| 		}, nil
 | |
| 	default:
 | |
| 		return PathElement{}, ErrUnknownPathElementType
 | |
| 	}
 | |
| }
 | |
| 
 | |
| var (
 | |
| 	readPool  = jsoniter.NewIterator(jsoniter.ConfigCompatibleWithStandardLibrary).Pool()
 | |
| 	writePool = jsoniter.NewStream(jsoniter.ConfigCompatibleWithStandardLibrary, nil, 1024).Pool()
 | |
| )
 | |
| 
 | |
| // SerializePathElement serializes a path element
 | |
| func SerializePathElement(pe PathElement) (string, error) {
 | |
| 	buf := strings.Builder{}
 | |
| 	err := serializePathElementToWriter(&buf, pe)
 | |
| 	return buf.String(), err
 | |
| }
 | |
| 
 | |
| func serializePathElementToWriter(w io.Writer, pe PathElement) error {
 | |
| 	stream := writePool.BorrowStream(w)
 | |
| 	defer writePool.ReturnStream(stream)
 | |
| 	switch {
 | |
| 	case pe.FieldName != nil:
 | |
| 		if _, err := stream.Write(peFieldSepBytes); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		stream.WriteRaw(*pe.FieldName)
 | |
| 	case pe.Key != nil:
 | |
| 		if _, err := stream.Write(peKeySepBytes); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		stream.WriteObjectStart()
 | |
| 
 | |
| 		for i, field := range *pe.Key {
 | |
| 			if i > 0 {
 | |
| 				stream.WriteMore()
 | |
| 			}
 | |
| 			stream.WriteObjectField(field.Name)
 | |
| 			value.WriteJSONStream(field.Value, stream)
 | |
| 		}
 | |
| 		stream.WriteObjectEnd()
 | |
| 	case pe.Value != nil:
 | |
| 		if _, err := stream.Write(peValueSepBytes); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		value.WriteJSONStream(*pe.Value, stream)
 | |
| 	case pe.Index != nil:
 | |
| 		if _, err := stream.Write(peIndexSepBytes); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		stream.WriteInt(*pe.Index)
 | |
| 	default:
 | |
| 		return errors.New("invalid PathElement")
 | |
| 	}
 | |
| 	b := stream.Buffer()
 | |
| 	err := stream.Flush()
 | |
| 	// Help jsoniter manage its buffers--without this, the next
 | |
| 	// use of the stream is likely to require an allocation. Look
 | |
| 	// at the jsoniter stream code to understand why. They were probably
 | |
| 	// optimizing for folks using the buffer directly.
 | |
| 	stream.SetBuffer(b[:0])
 | |
| 	return err
 | |
| }
 |