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

Add JSON/YAML marshalling/unmarshalling #5

Merged
merged 3 commits into from
Sep 5, 2023
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
74 changes: 67 additions & 7 deletions collection.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package version

import "fmt"
import (
"encoding/json"
"fmt"
)

// Collection is a type that implements the sort.Interface interface
// so that versions can be sorted.
Expand All @@ -18,14 +21,71 @@ func NewCollection(versions ...string) (Collection, error) {
return c, nil
}

func (v Collection) Len() int {
return len(v)
func (c Collection) Len() int {
return len(c)
}

func (v Collection) Less(i, j int) bool {
return v[i].Compare(v[j]) < 0
func (c Collection) Less(i, j int) bool {
return c[i].Compare(c[j]) < 0
}

func (v Collection) Swap(i, j int) {
v[i], v[j] = v[j], v[i]
func (c Collection) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
}

func (c *Collection) marshal() ([]string, error) {
strSlice := make([]string, len(*c))
for i, v := range *c {
s, err := v.MarshalJSON()
if err != nil {
return nil, err
}
strSlice[i] = string(s)
}
return strSlice, nil
}

func (c *Collection) unmarshal(strSlice []string) error {
coll := make(Collection, len(strSlice))
for i, s := range strSlice {
v, err := NewVersion(s)
if err != nil {
return err
}
coll[i] = v
}
*c = coll
return nil
}

// UnmarshalText implements the json.Marshaler interface.
func (c *Collection) MarshalJSON() ([]byte, error) {
strSlice, err := c.marshal()
if err != nil {
return nil, err
}
return json.Marshal(strSlice)
}

// UnmarshalJSON implements the json.Unmarshaler interface.
func (c *Collection) UnmarshalJSON(data []byte) error {
var strSlice []string
if err := json.Unmarshal(data, &strSlice); err != nil {
return fmt.Errorf("failed to decode JSON input: %w", err)
}
return c.unmarshal(strSlice)
}

// MarshalYAML implements the yaml.Marshaler interface.
func (c *Collection) MarshalYAML() (interface{}, error) {
return c.marshal()
}

// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *Collection) UnmarshalYAML(unmarshal func(interface{}) error) error {
var strSlice []string
if err := unmarshal(&strSlice); err != nil {
return fmt.Errorf("failed to decode YAML input: %w", err)
}
return c.unmarshal(strSlice)
}
59 changes: 59 additions & 0 deletions collection_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package version

import (
"encoding/json"
"sort"
"testing"

Expand Down Expand Up @@ -33,3 +34,61 @@ func TestSorting(t *testing.T) {
assert.Equal(t, "v1.21.2-beta.1+k0s.0", c[3].String())
assert.Equal(t, "v1.21.2+k0s.0", c[4].String())
}

func TestCollectionMarshalling(t *testing.T) {
c, err := NewCollection("v1.0.0+k0s.0", "v1.0.1+k0s.0")
assert.NoError(t, err)

t.Run("JSON", func(t *testing.T) {
jsonData, err := json.Marshal(c)
assert.NoError(t, err)
assert.Equal(t, `["v1.0.0+k0s.0","v1.0.1+k0s.0"]`, string(jsonData))
})

t.Run("YAML", func(t *testing.T) {
yamlData, err := c.MarshalYAML()
assert.NoError(t, err)
assert.Equal(t, []string{`"v1.0.0+k0s.0"`, `"v1.0.1+k0s.0"`}, yamlData)
})
}

func TestCollectionUnmarshalling(t *testing.T) {
t.Run("JSON", func(t *testing.T) {
var c Collection
err := json.Unmarshal([]byte(`["v1.0.0+k0s.1","v1.0.1+k0s.1"]`), &c)
assert.NoError(t, err)
assert.Equal(t, "v1.0.0+k0s.1", c[0].String())
assert.Equal(t, "v1.0.1+k0s.1", c[1].String())
})

t.Run("YAML", func(t *testing.T) {
var c Collection

err := c.UnmarshalYAML(func(i interface{}) error {
*(i.(*[]string)) = []string{"v1.0.0+k0s.1", "v1.0.1+k0s.1"}
return nil
})
assert.NoError(t, err)
assert.Equal(t, "v1.0.0+k0s.1", c[0].String())
assert.Equal(t, "v1.0.1+k0s.1", c[1].String())
})
}

func TestFailingCollectionUnmarshalling(t *testing.T) {
t.Run("JSON", func(t *testing.T) {
var c Collection
err := json.Unmarshal([]byte(`invalid_json`), &c)
assert.Error(t, err)
err = json.Unmarshal([]byte(`["invalid_version"]`), &c)
assert.Error(t, err)
})

t.Run("YAML", func(t *testing.T) {
var c Collection
err := c.UnmarshalYAML(func(i interface{}) error {
*(i.(*[]string)) = []string{"invalid\n"}
return nil
})
assert.Error(t, err)
})
}
37 changes: 37 additions & 0 deletions version.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,43 @@ func (v *Version) Compare(b *Version) int {
return 1
}

// MarshalJSON implements the json.Marshaler interface.
func (v *Version) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("\"%s\"", v.String())), nil
}

// MarshalYAML implements the yaml.Marshaler interface.
func (v *Version) MarshalYAML() (interface{}, error) {
return v.String(), nil
}

func (v *Version) unmarshal(f func(interface{}) error) error {
var s string
if err := f(&s); err != nil {
return fmt.Errorf("failed to decode input: %w", err)
}
newV, err := NewVersion(s)
if err != nil {
return fmt.Errorf("failed to unmarshal version: %w", err)
}
*v = *newV
return nil
}

// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (v *Version) UnmarshalYAML(f func(interface{}) error) error {
return v.unmarshal(f)
}

// UnmarshalJSON implements the json.Unmarshaler interface.
func (v *Version) UnmarshalJSON(b []byte) error {
s := strings.TrimSpace(strings.Trim(string(b), "\""))
return v.unmarshal(func(i interface{}) error {
*(i.(*string)) = s
return nil
})
}

// NewVersion returns a new Version created from the supplied string or an error if the string is not a valid version number
func NewVersion(v string) (*Version, error) {
n, err := goversion.NewVersion(strings.TrimPrefix(v, "v"))
Expand Down
61 changes: 61 additions & 0 deletions version_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package version

import (
"encoding/json"
"errors"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -44,3 +46,62 @@ func TestURLs(t *testing.T) {
assert.Equal(t, "https://github.com/k0sproject/k0s/releases/download/v1.23.3%2Bk0s.1/k0s-v1.23.3+k0s.1-arm64", a.DownloadURL("linux", "arm64"))
assert.Equal(t, "https://docs.k0sproject.io/v1.23.3+k0s.1/", a.DocsURL())
}

func TestMarshalling(t *testing.T) {
v, err := NewVersion("v1.0.0+k0s.0")
assert.NoError(t, err)

t.Run("JSON", func(t *testing.T) {
jsonData, err := json.Marshal(v)
assert.NoError(t, err)
assert.Equal(t, `"v1.0.0+k0s.0"`, string(jsonData))
})

t.Run("YAML", func(t *testing.T) {
yamlData, err := v.MarshalYAML()
assert.NoError(t, err)
assert.Equal(t, "v1.0.0+k0s.0", yamlData)
})
}

func TestUnmarshalling(t *testing.T) {
t.Run("JSON", func(t *testing.T) {
var v Version
err := json.Unmarshal([]byte(`"v1.0.0+k0s.1"`), &v)
assert.NoError(t, err)
assert.Equal(t, "v1.0.0+k0s.1", v.String())
})

t.Run("YAML", func(t *testing.T) {
v := &Version{}
err := v.UnmarshalYAML(func(i interface{}) error {
*(i.(*string)) = "v1.0.0+k0s.1"
return nil
})
assert.NoError(t, err)
assert.Equal(t, "v1.0.0+k0s.1", v.String())
})
}

func TestFailingUnmarshalling(t *testing.T) {
t.Run("JSON", func(t *testing.T) {
var v Version
err := json.Unmarshal([]byte(`invalid_json`), &v)
assert.Error(t, err)
err = json.Unmarshal([]byte(`"invalid_version"`), &v)
assert.Error(t, err)
})

t.Run("YAML", func(t *testing.T) {
var v = &Version{}
err := v.UnmarshalYAML(func(i interface{}) error {
return errors.New("forced error")
})
assert.Error(t, err)
err = v.UnmarshalYAML(func(i interface{}) error {
*(i.(*string)) = "invalid_version"
return nil
})
assert.Error(t, err)
})
}
Loading