Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

keep order from api response #44

Merged
merged 1 commit into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading