Skip to content

Commit

Permalink
keep order from api response (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
strokyl authored Jun 21, 2024
1 parent 8f408d3 commit 4695534
Show file tree
Hide file tree
Showing 5 changed files with 321 additions and 18 deletions.
18 changes: 13 additions & 5 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"os"
"strings"

"github.com/conduktor/ctl/orderedjson"
"github.com/conduktor/ctl/printutils"
"github.com/conduktor/ctl/resource"
"github.com/conduktor/ctl/schema"
"github.com/conduktor/ctl/utils"
"github.com/go-resty/resty/v2"
"os"
"strings"
)

type Client struct {
Expand Down Expand Up @@ -157,12 +159,18 @@ func (client *Client) Apply(resource *resource.Resource, dryMode bool) (string,
}

func printResponseAsYaml(bytes []byte) error {
var data interface{}
var data orderedjson.OrderedData //using this instead of interface{} keep json order
var finalData interface{} // in case it does not work we will failback to deserializing directly to interface{}
err := json.Unmarshal(bytes, &data)
if err != nil {
return err
err = json.Unmarshal(bytes, &finalData)
if err != nil {
return err
}
} else {
finalData = data
}
return printutils.PrintResourceLikeYamlFile(os.Stdout, data)
return printutils.PrintResourceLikeYamlFile(os.Stdout, finalData)
}

func (client *Client) Get(kind *schema.Kind, parentPathValue []string) error {
Expand Down
65 changes: 65 additions & 0 deletions orderedjson/orderedjson.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package orderedjson

import (
"encoding/json"

orderedmap "github.com/wk8/go-ordered-map/v2"
yaml "gopkg.in/yaml.v3"
)

type OrderedData struct {
orderedMap *orderedmap.OrderedMap[string, OrderedData]
array *[]OrderedData
fallback *interface{}
}

func (orderedData *OrderedData) UnmarshalJSON(data []byte) error {
orderedData.orderedMap = orderedmap.New[string, OrderedData]()
err := json.Unmarshal(data, &orderedData.orderedMap)
if err != nil {
orderedData.orderedMap = nil
orderedData.array = new([]OrderedData)
err = json.Unmarshal(data, orderedData.array)
}
if err != nil {
orderedData.array = nil
orderedData.fallback = new(interface{})
err = json.Unmarshal(data, &orderedData.fallback)
}
return err
}

// TODO: remove once hack in printYaml is not needed anymore
func (orderedData *OrderedData) GetMapOrNil() *orderedmap.OrderedMap[string, OrderedData] {
return orderedData.orderedMap
}

// TODO: remove once hack in printYaml is not needed anymore
func (orderedData *OrderedData) GetArrayOrNil() *[]OrderedData {
return orderedData.array
}

func (orderedData OrderedData) MarshalJSON() ([]byte, error) {
if orderedData.orderedMap != nil {
return json.Marshal(orderedData.orderedMap)
} else if orderedData.array != nil {
return json.Marshal(orderedData.array)
} else if orderedData.fallback != nil {
return json.Marshal(orderedData.fallback)
} else {
return json.Marshal(nil)
}
}

func (orderedData OrderedData) MarshalYAML() (interface{}, error) {
if orderedData.orderedMap != nil {
return orderedData.orderedMap, nil
} else if orderedData.array != nil {
return orderedData.array, nil
}
return orderedData.fallback, nil
}

func (orderedData *OrderedData) UnmarshalYAML(value *yaml.Node) error {
panic("Not supported")
}
79 changes: 79 additions & 0 deletions orderedjson/orderingjson_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package orderedjson

import (
"encoding/json"
"fmt"
"testing"

yaml "gopkg.in/yaml.v3"
)

func TestOrderedRecursiveMap(t *testing.T) {
testForJson(t, `{"name":"John","age":30,"city":"New York","children":[{"name":"Alice","age":5},{"name":"Bob","age":7}],"parent":{"name":"Jane","age":60,"city":"New York"}}`)
testForJson(t, `"yo"`)
testForJson(t, `true`)
testForJson(t, `false`)
testForJson(t, `42`)
testForJson(t, `42.2`)
testForJson(t, `[]`)
testForJson(t, `{}`)
testForJson(t, `{"z":{"x":{"v":{}}},"y":{"u":{"t":"p"}}}`)
testForJson(t, `[[[[]]]]`)
testForJson(t, `[{"z":42},{"b":{},"y":41,"a":[[{"z":42},{"b":{},"y":41,"a":[[{"z":42},{"b":{},"y":41,"a":[[{"z":42},{"b":{},"y":41,"a":[]}]]}]]}]]}]`)
}

func testForJson(t *testing.T, originalJSON string) {
// Unmarshal the JSON into an OrderedRecursiveMap
var omap OrderedData
err := json.Unmarshal([]byte(originalJSON), &omap)
if err != nil {
t.Fatalf("Failed to unmarshal JSON: %+v", err)
}

fmt.Printf("%v\n", omap)
// Marshal the OrderedRecursiveMap back into JSON
marshaledJSON, err := json.Marshal(&omap)
if err != nil {
t.Fatalf("Failed to marshal OrderedRecursiveMap: %v", err)
}

// Check if the original JSON and the marshaled JSON are the same
if originalJSON != string(marshaledJSON) {
t.Errorf("Original JSON and marshaled JSON do not match. Original: %s, Marshaled: %s", originalJSON, string(marshaledJSON))
}
}

func TestYamlMarshallingKeepOrderTo(t *testing.T) {
// Unmarshal the JSON into an OrderedRecursiveMap
var omap OrderedData
err := json.Unmarshal([]byte(`{"name":"John","age":30,"city":"New York","children":[{"name":"Alice","age":5},{"name":"Bob","age":7}],"parent":{"name":"Jane","age":60,"city":"New York"}}`), &omap)
if err != nil {
t.Fatalf("Failed to unmarshal JSON: %+v", err)
}

fmt.Printf("%v\n", omap)
// Marshal the OrderedRecursiveMap back into JSON
marshaledYaml, err := yaml.Marshal(&omap)
if err != nil {
t.Fatalf("Failed to marshal OrderedRecursiveMap: %v", err)
}

expected := `name: John
age: 30
city: New York
children:
- name: Alice
age: 5
- name: Bob
age: 7
parent:
name: Jane
age: 60
city: New York
`

// Check if the original JSON and the marshaled JSON are the same
if expected != string(marshaledYaml) {
t.Errorf("Marshalled yaml is not valid. Got:\n##\n%s\n##\n,\nMarshaled:\n##\n%s\n##", string(marshaledYaml), expected)
}
}
66 changes: 55 additions & 11 deletions printutils/printYaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"io"
"slices"

"github.com/conduktor/ctl/orderedjson"
orderedmap "github.com/wk8/go-ordered-map/v2"
yaml "gopkg.in/yaml.v3"
)

Expand All @@ -21,6 +23,7 @@ func printKeyYaml(w io.Writer, key string, data interface{}) error {
return nil
}

// TODO: delete once backend properly send resource fields in correct order
// this print a interface that is expected to a be a resource
// with the following field "version", "kind", "spec", "metadata"
// wit the field in a defined order.
Expand All @@ -31,21 +34,49 @@ func printResource(w io.Writer, data interface{}) error {
if err != nil {
return err
}
asMap, ok := data.(map[string]interface{})
if !ok {
fmt.Fprint(w, string(yamlBytes))
asMap, isMap := data.(map[string]interface{})
orderedData, isOrderedData := data.(orderedjson.OrderedData)
isOrderedMap := false
var asOrderedMap *orderedmap.OrderedMap[string, orderedjson.OrderedData]
if isOrderedData {
asOrderedMap = orderedData.GetMapOrNil()
isOrderedMap = asOrderedMap != nil
}
if isOrderedMap {
printResourceOrderedMapInCorrectOrder(w, *asOrderedMap)
} else if isMap {
printResourceMapInCorrectOrder(w, asMap)
} else {
wantedKeys := []string{"apiVersion", "kind", "metadata", "spec"}
for _, wantedKey := range wantedKeys {
printKeyYaml(w, wantedKey, asMap[wantedKey])
fmt.Fprint(w, string(yamlBytes))
}
return err
}

func printResourceMapInCorrectOrder(w io.Writer, dataAsMap map[string]interface{}) {
wantedKeys := []string{"apiVersion", "kind", "metadata", "spec"}
for _, wantedKey := range wantedKeys {
printKeyYaml(w, wantedKey, dataAsMap[wantedKey])
}
for otherKey, data := range dataAsMap {
if !slices.Contains(wantedKeys, otherKey) {
printKeyYaml(w, otherKey, data)
}
for otherKey, data := range asMap {
if !slices.Contains(wantedKeys, otherKey) {
printKeyYaml(w, otherKey, data)
}
}
}

func printResourceOrderedMapInCorrectOrder(w io.Writer, dataAsMap orderedmap.OrderedMap[string, orderedjson.OrderedData]) {
wantedKeys := []string{"apiVersion", "kind", "metadata", "spec"}
for _, wantedKey := range wantedKeys {
value, ok := dataAsMap.Get(wantedKey)
if ok {
printKeyYaml(w, wantedKey, value)
}
}
for pair := dataAsMap.Oldest(); pair != nil; pair = pair.Next() {
if !slices.Contains(wantedKeys, pair.Key) {
printKeyYaml(w, pair.Key, pair.Value)
}
}
return err
}

// take a interface that can be a resource or multiple resource
Expand All @@ -60,6 +91,19 @@ func PrintResourceLikeYamlFile(w io.Writer, data interface{}) error {
return err
}
}
case orderedjson.OrderedData:
array := dataType.GetArrayOrNil()
if array == nil {
return printResource(w, data)
}

for _, d := range *array {
fmt.Fprintln(w, "---")
err := printResource(w, d)
if err != nil {
return err
}
}
default:
return printResource(w, data)
}
Expand Down
Loading

0 comments on commit 4695534

Please sign in to comment.