Encode/Decode PathElement to/from strings

This commit is contained in:
Antoine Pelisse
2018-11-28 08:48:56 -08:00
committed by jennybuckley
parent 16df5bb7bb
commit df76269a30
5 changed files with 231 additions and 10 deletions

8
Godeps/Godeps.json generated
View File

@@ -4069,7 +4069,7 @@
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath",
"Rev": "01332b709372d8d83b4d7653b30a1961680a47d5"
"Rev": "d45b5c336b0e2bdd0b58ae4181caba85f24c6257"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/merge",
@@ -4077,15 +4077,15 @@
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/schema",
"Rev": "01332b709372d8d83b4d7653b30a1961680a47d5"
"Rev": "d45b5c336b0e2bdd0b58ae4181caba85f24c6257"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/typed",
"Rev": "01332b709372d8d83b4d7653b30a1961680a47d5"
"Rev": "d45b5c336b0e2bdd0b58ae4181caba85f24c6257"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/value",
"Rev": "01332b709372d8d83b4d7653b30a1961680a47d5"
"Rev": "d45b5c336b0e2bdd0b58ae4181caba85f24c6257"
},
{
"ImportPath": "sigs.k8s.io/yaml",

View File

@@ -0,0 +1,122 @@
/*
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 apply
import (
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/value"
)
const (
Field = "f"
Value = "v"
Index = "i"
Key = "k"
Separator = ":"
)
func NewPathElement(s string) (fieldpath.PathElement, error) {
split := strings.SplitN(s, Separator, 2)
if len(split) < 2 {
return fieldpath.PathElement{}, fmt.Errorf("missing colon: %v", s)
}
switch split[0] {
case Field:
return fieldpath.PathElement{
FieldName: &split[1],
}, nil
case Value:
val, err := value.FromJSON([]byte(split[1]))
if err != nil {
return fieldpath.PathElement{}, err
}
return fieldpath.PathElement{
Value: &val,
}, nil
case Index:
i, err := strconv.Atoi(split[1])
if err != nil {
return fieldpath.PathElement{}, err
}
return fieldpath.PathElement{
Index: &i,
}, nil
case Key:
kv := map[string]string{}
err := json.Unmarshal([]byte(split[1]), &kv)
if err != nil {
return fieldpath.PathElement{}, err
}
fields := []value.Field{}
for k, v := range kv {
fmt.Println(v)
val, err := value.FromJSON([]byte(v))
if err != nil {
return fieldpath.PathElement{}, err
}
fields = append(fields, value.Field{
Name: k,
Value: val,
})
}
return fieldpath.PathElement{
Key: fields,
}, nil
default:
// Ignore unknown key types
return fieldpath.PathElement{}, nil
}
}
func PathElementString(pe fieldpath.PathElement) (string, error) {
switch {
case pe.FieldName != nil:
return Field + Separator + *pe.FieldName, nil
case len(pe.Key) > 0:
kv := map[string]string{}
for _, k := range pe.Key {
b, err := k.Value.ToJSON()
if err != nil {
return "", err
}
kv[k.Name] = string(b)
}
b, err := json.Marshal(kv)
if err != nil {
return "", err
}
return Key + ":" + string(b), nil
case pe.Value != nil:
b, err := pe.Value.ToJSON()
if err != nil {
return "", err
}
return Value + ":" + string(b), nil
case pe.Index != nil:
return Index + ":" + strconv.Itoa(*pe.Index), nil
default:
return "", errors.New("Invalid type of path element")
}
}

View File

@@ -0,0 +1,81 @@
/*
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 apply
import "testing"
func TestPathElementRoundTrip(t *testing.T) {
tests := []string{
"i:0",
"i:1234",
"f:",
"f:spec",
"f:more-complicated-string",
"k:{\"name\":\"\\\"my-container\\\"\"}",
"k:{\"port\":\"\\\"8080\\\"\",\"protocol\":\"\\\"TCP\\\"\"}",
"v:null",
"v:\"some-string\"",
"v:1234",
"v:{\"some\":\"json\"}",
}
for _, test := range tests {
t.Run(test, func(t *testing.T) {
pe, err := NewPathElement(test)
if err != nil {
t.Fatalf("Failed to create path element: %v", err)
}
output, err := PathElementString(pe)
if err != nil {
t.Fatalf("Failed to create string from path element: %v", err)
}
if test != output {
t.Fatalf("Expected round-trip:\ninput: %v\noutput: %v", test, output)
}
})
}
}
func TestPathElementIgnoreUnknown(t *testing.T) {
_, err := NewPathElement("r:Hello")
if err != nil {
t.Fatalf("Unknown qualifiers should be ignored")
}
}
func TestNewPathElementError(t *testing.T) {
tests := []string{
"",
"no-colon",
"i:index is not a number",
"i:1.23",
"i:",
"v:invalid json",
"v:",
"k:invalid json",
"k:{\"name\":\"invalid json\"}",
}
for _, test := range tests {
t.Run(test, func(t *testing.T) {
_, err := NewPathElement(test)
if err == nil {
t.Fatalf("Expected error, no error found")
}
})
}
}

View File

@@ -5,6 +5,7 @@ go_library(
srcs = [
"gvkparser.go",
"managedfields.go",
"pathelement.go",
"typeconverter.go",
"versionconverter.go",
],
@@ -31,6 +32,7 @@ go_test(
name = "go_default_test",
srcs = [
"managedfields_test.go",
"pathelement_test.go",
"typeconverter_test.go",
"versionconverter_test.go",
],

View File

@@ -17,6 +17,7 @@ limitations under the License.
package value
import (
"encoding/json"
"fmt"
"gopkg.in/yaml.v2"
@@ -53,6 +54,21 @@ func FromYAML(input []byte) (Value, error) {
return v, nil
}
// FromJSON is a helper function for reading a JSON document
func FromJSON(input []byte) (Value, error) {
var decoded interface{}
if err := json.Unmarshal(input, &decoded); err != nil {
return Value{}, err
}
v, err := FromUnstructured(decoded)
if err != nil {
return Value{}, fmt.Errorf("failed to interpret (%v):\n%s", err, input)
}
return v, nil
}
// FromUnstructured will convert a go interface to a Value.
// It's most commonly expected to be used with map[string]interface{} as the
// input. `in` must not have any structures with cycles in them.
@@ -156,12 +172,12 @@ func FromUnstructured(in interface{}) (Value, error) {
// preserve order of keys within maps/structs. This is as a convenience to
// humans keeping YAML documents, not because there is a behavior difference.
func (v *Value) ToYAML() ([]byte, error) {
out := v.ToUnstructured(true)
b, err := yaml.Marshal(out)
if err != nil {
return nil, err
}
return b, nil
return yaml.Marshal(v.ToUnstructured(true))
}
// ToJSON is a helper function for producing a JSon document.
func (v *Value) ToJSON() ([]byte, error) {
return json.Marshal(v.ToUnstructured(false))
}
// ToUnstructured will convert the Value into a go-typed object.