Skip to content
This repository was archived by the owner on Sep 9, 2020. It is now read-only.

JSON marshalers for manifest and lock #16

Merged
merged 10 commits into from
Dec 1, 2016
40 changes: 40 additions & 0 deletions lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,43 @@ func (l *lock) InputHash() []byte {
func (l *lock) Projects() []gps.LockedProject {
return l.P
}

func (l *lock) MarshalJSON() ([]byte, error) {
raw := rawLock{
Memo: hex.EncodeToString(l.Memo),
P: make([]lockedDep, len(l.P)),
}

for k, lp := range l.P {
id := lp.Ident()
ld := lockedDep{
Name: string(id.ProjectRoot),
Repository: id.NetworkName,
Packages: lp.Packages(),
}

v := lp.Version()
// Figure out how to get the underlying revision
switch tv := v.(type) {
case gps.UnpairedVersion:
// TODO we could error here, if we want to be very defensive about not
// allowing a lock to be written if without an immmutable revision
case gps.Revision:
ld.Revision = tv.String()
case gps.PairedVersion:
ld.Revision = tv.Underlying().String()
}

switch v.Type() {
case "branch":
ld.Branch = v.String()
case "semver", "version":
ld.Version = v.String()
}

raw.P[k] = ld
}

// TODO sort output - #15
return json.Marshal(raw)
}
76 changes: 55 additions & 21 deletions lock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,46 @@
package main

import (
"bytes"
"encoding/hex"
"encoding/json"
"reflect"
"strings"
"testing"

"github.com/sdboyer/gps"
)

func TestReadLock(t *testing.T) {
const le = `{
"memo": "2252a285ab27944a4d7adcba8dbd03980f59ba652f12db39fa93b927c345593e",
"projects": [
{
"name": "github.com/sdboyer/gps",
"branch": "master",
const le = `{
"memo": "2252a285ab27944a4d7adcba8dbd03980f59ba652f12db39fa93b927c345593e",
"projects": [
{
"name": "github.com/sdboyer/gps",
"branch": "master",
"version": "v0.12.0",
"revision": "d05d5aca9f895d19e9265839bffeadd74a2d2ecb",
"packages": ["."]
}
]
"revision": "d05d5aca9f895d19e9265839bffeadd74a2d2ecb",
"packages": [
"."
]
}
]
}`
const lg = `{
"memo": "2252a285ab27944a4d7adcba8dbd03980f59ba652f12db39fa93b927c345593e",
"projects": [
{
"name": "github.com/sdboyer/gps",
"branch": "master",
"revision": "d05d5aca9f895d19e9265839bffeadd74a2d2ecb",
"packages": ["."]
}
]

const lg = `{
"memo": "2252a285ab27944a4d7adcba8dbd03980f59ba652f12db39fa93b927c345593e",
"projects": [
{
"name": "github.com/sdboyer/gps",
"branch": "master",
"revision": "d05d5aca9f895d19e9265839bffeadd74a2d2ecb",
"packages": [
"."
]
}
]
}`

func TestReadLock(t *testing.T) {
_, err := readLock(strings.NewReader(le))
if err == nil {
t.Error("Reading lock with invalid props should have caused error, but did not")
Expand Down Expand Up @@ -66,3 +73,30 @@ func TestReadLock(t *testing.T) {
t.Error("Valid lock did not parse as expected")
}
}

func TestWriteLock(t *testing.T) {
memo, _ := hex.DecodeString("2252a285ab27944a4d7adcba8dbd03980f59ba652f12db39fa93b927c345593e")
l := &lock{
Memo: memo,
P: []gps.LockedProject{
gps.NewLockedProject(
gps.ProjectIdentifier{ProjectRoot: gps.ProjectRoot("github.com/sdboyer/gps")},
gps.NewBranch("master").Is(gps.Revision("d05d5aca9f895d19e9265839bffeadd74a2d2ecb")),
[]string{"."},
),
},
}

b, err := json.Marshal(l)
if err != nil {
t.Fatalf("Error while marshaling valid lock to JSON: %q", err)
}

var out bytes.Buffer
json.Indent(&out, b, "", "\t")

s := out.String()
if s != lg {
t.Errorf("Valid lock did not marshal to JSON as expected:\n\t(GOT): %s\n\t(WNT): %s", s, lg)
}
}
67 changes: 63 additions & 4 deletions manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package main

import (
"bytes"
"encoding/json"
"fmt"
"io"
Expand All @@ -19,16 +20,16 @@ type manifest struct {
}

type rawManifest struct {
Dependencies map[string]possibleProps `json:"dependencies"`
Overrides map[string]possibleProps `json:"overrides"`
Ignores []string `json:"ignores"`
Dependencies map[string]possibleProps `json:"dependencies,omitempty"`
Overrides map[string]possibleProps `json:"overrides,omitempty"`
Ignores []string `json:"ignores,omitempty"`
}

type possibleProps struct {
Branch string `json:"branch,omitempty"`
Revision string `json:"revision,omitempty"`
Version string `json:"version,omitempty"`
NetworkName string `json:"network_name,omitempty"`
NetworkName string `json:"source,omitempty"`
}

func newRawManifest() rawManifest {
Expand Down Expand Up @@ -69,6 +70,10 @@ func readManifest(r io.Reader) (*manifest, error) {
return m, nil
}

// toProps interprets the string representations of project information held in
// a possibleProps, converting them into a proper gps.ProjectProperties. An
// error is returned if the possibleProps contains some invalid combination -
// for example, if both a branch and version constraint are specified.
func toProps(n string, p possibleProps) (pp gps.ProjectProperties, err error) {
if p.Branch != "" {
if p.Version != "" || p.Revision != "" {
Expand Down Expand Up @@ -98,6 +103,60 @@ func toProps(n string, p possibleProps) (pp gps.ProjectProperties, err error) {
return pp, nil
}

func (m *manifest) MarshalJSON() ([]byte, error) {
raw := rawManifest{
Dependencies: make(map[string]possibleProps, len(m.Dependencies)),
Overrides: make(map[string]possibleProps, len(m.Ovr)),
Ignores: m.Ignores,
}

for n, pp := range m.Dependencies {
raw.Dependencies[string(n)] = toPossible(pp)
}

for n, pp := range m.Ovr {
raw.Overrides[string(n)] = toPossible(pp)
}

b, err := json.Marshal(raw)
if err != nil {
return nil, err
}

// Semver range ops, > and <, get turned into unicode code points. This is a
// nice example of why using JSON for files like this is not the best
b = bytes.Replace(b, []byte("\\u003c"), []byte("<"), -1)
b = bytes.Replace(b, []byte("\\u003e"), []byte(">"), -1)
return b, nil
}

func toPossible(pp gps.ProjectProperties) (p possibleProps) {
p.NetworkName = pp.NetworkName

if v, ok := pp.Constraint.(gps.Version); ok {
switch v.Type() {
case "rev": // will be changed to revision upstream soon
p.Revision = v.String()
case "branch":
p.Branch = v.String()
case "semver", "version":
p.Version = v.String()
}
} else {
// We simply don't allow for a case where the user could directly
// express a 'none' constraint, so we can ignore it here. We also ignore
// the 'any' case, because that's the other possibility, and it's what
// we interpret not having any constraint expressions at all to mean.
//if !gps.IsAny(pp.Constraint) && !gps.IsNone(pp.Constraint) {
if !gps.IsAny(pp.Constraint) {
// Has to be a semver range.
p.Version = pp.Constraint.String()
}
}

return
}

func (m *manifest) DependencyConstraints() gps.ProjectConstraints {
return m.Dependencies
}
Expand Down
66 changes: 53 additions & 13 deletions manifest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,70 +5,72 @@
package main

import (
"bytes"
"encoding/json"
"reflect"
"strings"
"testing"

"github.com/sdboyer/gps"
)

func TestReadManifest(t *testing.T) {
const je = `{
const je = `{
"dependencies": {
"github.com/sdboyer/gps": {
"branch": "master",
"revision": "d05d5aca9f895d19e9265839bffeadd74a2d2ecb",
"version": "^v0.12.0",
"network_name": "https://github.com/sdboyer/gps"
"source": "https://github.com/sdboyer/gps"
}
},
"overrides": {
"github.com/sdboyer/gps": {
"branch": "master",
"revision": "d05d5aca9f895d19e9265839bffeadd74a2d2ecb",
"version": "^v0.12.0",
"network_name": "https://github.com/sdboyer/gps"
"source": "https://github.com/sdboyer/gps"
}
},
"ignores": [
"github.com/foo/bar"
]
}`

const jg = `{
const jg = `{
"dependencies": {
"github.com/sdboyer/gps": {
"version": "^v0.12.0"
},
"github.com/babble/brook": {
"revision": "d05d5aca9f895d19e9265839bffeadd74a2d2ecb"
},
"github.com/sdboyer/gps": {
"version": ">=0.12.0, <1.0.0"
}
},
"overrides": {
"github.com/sdboyer/gps": {
"branch": "master",
"network_name": "https://github.com/sdboyer/gps"
"source": "https://github.com/sdboyer/gps"
}
},
"ignores": [
"github.com/foo/bar"
]
}`

_, err := ReadManifest(strings.NewReader(je))
func TestReadManifest(t *testing.T) {
_, err := readManifest(strings.NewReader(je))
if err == nil {
t.Error("Reading manifest with invalid props should have caused error, but did not")
} else if !strings.Contains(err.Error(), "multiple constraints") {
t.Errorf("Unexpected error %q; expected multiple constraint error", err)
}

m2, err := ReadManifest(strings.NewReader(jg))
m2, err := readManifest(strings.NewReader(jg))
if err != nil {
t.Fatalf("Should have read Manifest correctly, but got err %q", err)
}

c, _ := gps.NewSemverConstraint("^v0.12.0")
em := Manifest{
c, _ := gps.NewSemverConstraint(">=0.12.0, <1.0.0")
em := manifest{
Dependencies: map[gps.ProjectRoot]gps.ProjectProperties{
gps.ProjectRoot("github.com/sdboyer/gps"): {
Constraint: c,
Expand Down Expand Up @@ -96,3 +98,41 @@ func TestReadManifest(t *testing.T) {
t.Error("Valid manifest's ignores did not parse as expected")
}
}

func TestWriteManifest(t *testing.T) {
c, _ := gps.NewSemverConstraint("^v0.12.0")
m := &manifest{
Dependencies: map[gps.ProjectRoot]gps.ProjectProperties{
gps.ProjectRoot("github.com/sdboyer/gps"): {
Constraint: c,
},
gps.ProjectRoot("github.com/babble/brook"): {
Constraint: gps.Revision("d05d5aca9f895d19e9265839bffeadd74a2d2ecb"),
},
},
Ovr: map[gps.ProjectRoot]gps.ProjectProperties{
gps.ProjectRoot("github.com/sdboyer/gps"): {
NetworkName: "https://github.com/sdboyer/gps",
Constraint: gps.NewBranch("master"),
},
},
Ignores: []string{"github.com/foo/bar"},
}

b, err := json.Marshal(m)
if err != nil {
t.Fatalf("Error while marshaling valid manifest to JSON: %q", err)
}

var out bytes.Buffer
json.Indent(&out, b, "", " ")
b = out.Bytes()
// uuuuuughhhhh
b = bytes.Replace(b, []byte("\\u003c"), []byte("<"), -1)
b = bytes.Replace(b, []byte("\\u003e"), []byte(">"), -1)

s := string(b)
if s != jg {
t.Errorf("Valid manifest did not marshal to JSON as expected:\n\t(GOT): %s\n\t(WNT): %s", s, jg)
}
}