From cdb95c128dc8e66546d2459a061dd313fc0f2744 Mon Sep 17 00:00:00 2001 From: Patrick O'brien Date: Wed, 7 Sep 2016 22:24:10 +1000 Subject: [PATCH] Add zero files, fix some mistakes --- bytes.go | 8 +-- bytes_test.go | 5 ++ json.go | 8 +-- json_test.go | 5 ++ zero/bytes.go | 128 ++++++++++++++++++++++++++++++++++ zero/bytes_test.go | 169 +++++++++++++++++++++++++++++++++++++++++++++ zero/json.go | 128 ++++++++++++++++++++++++++++++++++ zero/json_test.go | 169 +++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 612 insertions(+), 8 deletions(-) create mode 100644 zero/bytes.go create mode 100644 zero/bytes_test.go create mode 100644 zero/json.go create mode 100644 zero/json_test.go diff --git a/bytes.go b/bytes.go index 491a6fd..f8f4ca7 100644 --- a/bytes.go +++ b/bytes.go @@ -29,14 +29,14 @@ func NewBytes(b []byte, valid bool) Bytes { } } -// BytesFrom creates a new Bytes that will be null if len zero. +// BytesFrom creates a new Bytes that will be invalid if nil. func BytesFrom(b []byte) Bytes { - return NewBytes(b, len(b) != 0) + return NewBytes(b, b != nil) } -// BytesFromPtr creates a new Bytes that be null if len zero. +// BytesFromPtr creates a new Bytes that will be invalid if nil. func BytesFromPtr(b *[]byte) Bytes { - if b == nil || len(*b) == 0 { + if b == nil { return NewBytes(nil, false) } n := NewBytes(*b, true) diff --git a/bytes_test.go b/bytes_test.go index 31575c1..98b5b68 100644 --- a/bytes_test.go +++ b/bytes_test.go @@ -18,6 +18,11 @@ func TestBytesFrom(t *testing.T) { if zero.Valid { t.Error("BytesFrom(nil)", "is valid, but should be invalid") } + + zero = BytesFrom([]byte{}) + if !zero.Valid { + t.Error("BytesFrom([]byte{})", "is invalid, but should be valid") + } } func TestBytesFromPtr(t *testing.T) { diff --git a/json.go b/json.go index d1b037c..3dd3aa7 100644 --- a/json.go +++ b/json.go @@ -31,14 +31,14 @@ func NewJSON(b []byte, valid bool) JSON { } } -// JSONFrom creates a new JSON that will be null if len zero. +// JSONFrom creates a new JSON that will be invalid if nil. func JSONFrom(b []byte) JSON { - return NewJSON(b, len(b) != 0) + return NewJSON(b, b != nil) } -// JSONFromPtr creates a new JSON that be null if len zero. +// JSONFromPtr creates a new JSON that will be invalid if nil. func JSONFromPtr(b *[]byte) JSON { - if b == nil || len(*b) == 0 { + if b == nil { return NewJSON(nil, false) } n := NewJSON(*b, true) diff --git a/json_test.go b/json_test.go index f8b808f..934ad14 100644 --- a/json_test.go +++ b/json_test.go @@ -18,6 +18,11 @@ func TestJSONFrom(t *testing.T) { if zero.Valid { t.Error("JSONFrom(nil)", "is valid, but should be invalid") } + + zero = JSONFrom([]byte{}) + if !zero.Valid { + t.Error("JSONFrom([]byte{})", "is invalid, but should be valid") + } } func TestJSONFromPtr(t *testing.T) { diff --git a/zero/bytes.go b/zero/bytes.go new file mode 100644 index 0000000..3a6fd10 --- /dev/null +++ b/zero/bytes.go @@ -0,0 +1,128 @@ +package zero + +import ( + "database/sql/driver" + + "gopkg.in/nullbio/null.v5/convert" +) + +// NullBytes is a nullable byte slice. +type NullBytes struct { + Bytes []byte + Valid bool +} + +// Bytes is a nullable []byte. +// JSON marshals to zero if null. +// Considered null to SQL if zero. +type Bytes struct { + NullBytes +} + +// NewBytes creates a new Bytes +func NewBytes(b []byte, valid bool) Bytes { + return Bytes{ + NullBytes: NullBytes{ + Bytes: b, + Valid: valid, + }, + } +} + +// BytesFrom creates a new Bytes that will be null if len zero. +func BytesFrom(b []byte) Bytes { + return NewBytes(b, len(b) != 0) +} + +// BytesFromPtr creates a new Bytes that be null if len zero. +func BytesFromPtr(b *[]byte) Bytes { + if b == nil || len(*b) == 0 { + return NewBytes(nil, false) + } + n := NewBytes(*b, true) + return n +} + +// UnmarshalJSON implements json.Unmarshaler. +// If data is len 0 or nil, it will unmarshal to JSON null. +// If not, it will copy your data slice into Bytes. +func (b *Bytes) UnmarshalJSON(data []byte) error { + if data == nil || len(data) == 0 { + b.Bytes = []byte("null") + b.Valid = false + } else { + b.Bytes = append(b.Bytes[0:0], data...) + b.Valid = true + } + + return nil +} + +// UnmarshalText implements encoding.TextUnmarshaler. +// It will unmarshal to nil if the text is nil or len 0. +func (b *Bytes) UnmarshalText(text []byte) error { + if text == nil || len(text) == 0 { + b.Bytes = nil + b.Valid = false + } else { + b.Bytes = append(b.Bytes[0:0], text...) + b.Valid = true + } + + return nil +} + +// MarshalJSON implements json.Marshaler. +// It will encode null if the Bytes is nil. +func (b Bytes) MarshalJSON() ([]byte, error) { + if !b.Valid { + return []byte("null"), nil + } + return b.Bytes, nil +} + +// MarshalText implements encoding.TextMarshaler. +// It will encode nil if the Bytes is invalid. +func (b Bytes) MarshalText() ([]byte, error) { + if !b.Valid { + return nil, nil + } + return b.Bytes, nil +} + +// SetValid changes this Bytes's value and also sets it to be non-null. +func (b *Bytes) SetValid(n []byte) { + b.Bytes = n + b.Valid = true +} + +// Ptr returns a pointer to this Bytes's value, or a nil pointer if this Bytes is null. +func (b Bytes) Ptr() *[]byte { + if !b.Valid { + return nil + } + return &b.Bytes +} + +// IsZero returns true for null or zero Bytes's, for future omitempty support (Go 1.4?) +func (b Bytes) IsZero() bool { + return !b.Valid || b.Bytes == nil || len(b.Bytes) == 0 +} + +// Scan implements the Scanner interface. +func (n *NullBytes) Scan(value interface{}) error { + if value == nil { + n.Bytes, n.Valid = []byte{}, false + return nil + } + n.Valid = true + return convert.ConvertAssign(&n.Bytes, value) +} + +// Value implements the driver Valuer interface. +func (n NullBytes) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.Bytes, nil +} diff --git a/zero/bytes_test.go b/zero/bytes_test.go new file mode 100644 index 0000000..4612e0a --- /dev/null +++ b/zero/bytes_test.go @@ -0,0 +1,169 @@ +package zero + +import ( + "bytes" + "encoding/json" + "testing" +) + +var ( + bytesJSON = []byte(`"hello"`) +) + +func TestBytesFrom(t *testing.T) { + i := BytesFrom([]byte(`"hello"`)) + assertBytes(t, i, "BytesFrom()") + + zero := BytesFrom(nil) + if zero.Valid { + t.Error("BytesFrom(nil)", "is valid, but should be invalid") + } + + zero = BytesFrom([]byte{}) + if zero.Valid { + t.Error("BytesFrom([]byte{})", "is valid, but should be invalid") + } +} + +func TestBytesFromPtr(t *testing.T) { + n := []byte(`"hello"`) + iptr := &n + i := BytesFromPtr(iptr) + assertBytes(t, i, "BytesFromPtr()") + + null := BytesFromPtr(nil) + assertNullBytes(t, null, "BytesFromPtr(nil)") +} + +func TestUnmarshalBytes(t *testing.T) { + var i Bytes + err := json.Unmarshal(bytesJSON, &i) + maybePanic(err) + assertBytes(t, i, "[]byte json") + + var ni Bytes + err = ni.UnmarshalJSON([]byte{}) + if ni.Valid == true { + t.Errorf("expected Valid to be false, got true") + } + if !bytes.Equal(ni.Bytes, []byte("null")) { + t.Errorf("Expected Bytes to be nil slice, but was not: %#v %#v", ni.Bytes, []byte(`null`)) + } + + var null Bytes + err = null.UnmarshalJSON(nil) + if null.Valid == true { + t.Errorf("expected Valid to be false, got true") + } + if !bytes.Equal(null.Bytes, []byte(`null`)) { + t.Errorf("Expected Bytes to be []byte nil, but was not: %#v %#v", null.Bytes, []byte(`null`)) + } +} + +func TestTextUnmarshalBytes(t *testing.T) { + var i Bytes + err := i.UnmarshalText([]byte(`"hello"`)) + maybePanic(err) + assertBytes(t, i, "UnmarshalText() []byte") + + var blank Bytes + err = blank.UnmarshalText([]byte("")) + maybePanic(err) + assertNullBytes(t, blank, "UnmarshalText() empty []byte") +} + +func TestMarshalBytes(t *testing.T) { + i := BytesFrom([]byte(`"hello"`)) + data, err := json.Marshal(i) + maybePanic(err) + assertJSONEquals(t, data, `"hello"`, "non-empty json marshal") + + // invalid values should be encoded as null + null := NewBytes(nil, false) + data, err = json.Marshal(null) + maybePanic(err) + assertJSONEquals(t, data, "null", "null json marshal") +} + +func TestMarshalBytesText(t *testing.T) { + i := BytesFrom([]byte(`"hello"`)) + data, err := i.MarshalText() + maybePanic(err) + assertJSONEquals(t, data, `"hello"`, "non-empty text marshal") + + // invalid values should be encoded as null + null := NewBytes(nil, false) + data, err = null.MarshalText() + maybePanic(err) + assertJSONEquals(t, data, "", "null text marshal") +} + +func TestBytesPointer(t *testing.T) { + i := BytesFrom([]byte(`"hello"`)) + ptr := i.Ptr() + if !bytes.Equal(*ptr, []byte(`"hello"`)) { + t.Errorf("bad %s []byte: %#v ≠ %s\n", "pointer", ptr, `"hello"`) + } + + null := NewBytes(nil, false) + ptr = null.Ptr() + if ptr != nil { + t.Errorf("bad %s []byte: %#v ≠ %s\n", "nil pointer", ptr, "nil") + } +} + +func TestBytesIsZero(t *testing.T) { + i := BytesFrom([]byte(`"hello"`)) + if i.IsZero() { + t.Errorf("IsZero() should be false") + } + + null := NewBytes(nil, false) + if !null.IsZero() { + t.Errorf("IsZero() should be true") + } + + zero := NewBytes(nil, true) + if !zero.IsZero() { + t.Errorf("IsZero() should be true") + } + + nz := NewBytes([]byte("thing"), true) + if nz.IsZero() { + t.Error("IsZero() should be false") + } +} + +func TestBytesSetValid(t *testing.T) { + change := NewBytes(nil, false) + assertNullBytes(t, change, "SetValid()") + change.SetValid([]byte(`"hello"`)) + assertBytes(t, change, "SetValid()") +} + +func TestBytesScan(t *testing.T) { + var i Bytes + err := i.Scan(`"hello"`) + maybePanic(err) + assertBytes(t, i, "scanned []byte") + + var null Bytes + err = null.Scan(nil) + maybePanic(err) + assertNullBytes(t, null, "scanned null") +} + +func assertBytes(t *testing.T, i Bytes, from string) { + if !bytes.Equal(i.Bytes, []byte(`"hello"`)) { + t.Errorf("bad %s []byte: %#v ≠ %#v\n", from, string(i.Bytes), string([]byte(`"hello"`))) + } + if !i.Valid { + t.Error(from, "is invalid, but should be valid") + } +} + +func assertNullBytes(t *testing.T, i Bytes, from string) { + if i.Valid { + t.Error(from, "is valid, but should be invalid") + } +} diff --git a/zero/json.go b/zero/json.go new file mode 100644 index 0000000..75680c2 --- /dev/null +++ b/zero/json.go @@ -0,0 +1,128 @@ +package zero + +import ( + "database/sql/driver" + + "gopkg.in/nullbio/null.v5/convert" +) + +// NullJSON is a nullable byte slice. +type NullJSON struct { + JSON []byte + Valid bool +} + +// JSON is a nullable []byte. +// JSON marshals to zero if null. +// Considered null to SQL if zero. +type JSON struct { + NullJSON +} + +// NewJSON creates a new JSON +func NewJSON(b []byte, valid bool) JSON { + return JSON{ + NullJSON: NullJSON{ + JSON: b, + Valid: valid, + }, + } +} + +// JSONFrom creates a new JSON that will be null if len zero. +func JSONFrom(b []byte) JSON { + return NewJSON(b, len(b) != 0) +} + +// JSONFromPtr creates a new JSON that be null if len zero. +func JSONFromPtr(b *[]byte) JSON { + if b == nil || len(*b) == 0 { + return NewJSON(nil, false) + } + n := NewJSON(*b, true) + return n +} + +// UnmarshalJSON implements json.Unmarshaler. +// If data is len 0 or nil, it will unmarshal to JSON null. +// If not, it will copy your data slice into JSON. +func (j *JSON) UnmarshalJSON(data []byte) error { + if data == nil || len(data) == 0 { + j.JSON = []byte("null") + j.Valid = false + } else { + j.JSON = append(j.JSON[0:0], data...) + j.Valid = true + } + + return nil +} + +// UnmarshalText implements encoding.TextUnmarshaler. +// It will unmarshal to nil if the text is nil or len 0. +func (j *JSON) UnmarshalText(text []byte) error { + if text == nil || len(text) == 0 { + j.JSON = nil + j.Valid = false + } else { + j.JSON = append(j.JSON[0:0], text...) + j.Valid = true + } + + return nil +} + +// MarshalJSON implements json.Marshaler. +// It will encode null if the JSON is nil. +func (j JSON) MarshalJSON() ([]byte, error) { + if !j.Valid { + return []byte("null"), nil + } + return j.JSON, nil +} + +// MarshalText implements encoding.TextMarshaler. +// It will encode nil if the JSON is invalid. +func (j JSON) MarshalText() ([]byte, error) { + if !j.Valid { + return nil, nil + } + return j.JSON, nil +} + +// SetValid changes this JSON's value and also sets it to be non-null. +func (j *JSON) SetValid(n []byte) { + j.JSON = n + j.Valid = true +} + +// Ptr returns a pointer to this JSON's value, or a nil pointer if this JSON is null. +func (j JSON) Ptr() *[]byte { + if !j.Valid { + return nil + } + return &j.JSON +} + +// IsZero returns true for null or zero JSON's, for future omitempty support (Go 1.4?) +func (j JSON) IsZero() bool { + return !j.Valid || j.JSON == nil || len(j.JSON) == 0 +} + +// Scan implements the Scanner interface. +func (n *NullJSON) Scan(value interface{}) error { + if value == nil { + n.JSON, n.Valid = []byte{}, false + return nil + } + n.Valid = true + return convert.ConvertAssign(&n.JSON, value) +} + +// Value implements the driver Valuer interface. +func (n NullJSON) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.JSON, nil +} diff --git a/zero/json_test.go b/zero/json_test.go new file mode 100644 index 0000000..789ac01 --- /dev/null +++ b/zero/json_test.go @@ -0,0 +1,169 @@ +package zero + +import ( + "bytes" + "encoding/json" + "testing" +) + +var ( + jsonJSON = []byte(`"hello"`) +) + +func TestJSONFrom(t *testing.T) { + i := JSONFrom([]byte(`"hello"`)) + assertJSON(t, i, "JSONFrom()") + + zero := JSONFrom(nil) + if zero.Valid { + t.Error("JSONFrom(nil)", "is valid, but should be invalid") + } + + zero = JSONFrom([]byte{}) + if zero.Valid { + t.Error("JSONFrom([]byte{})", "is valid, but should be invalid") + } +} + +func TestJSONFromPtr(t *testing.T) { + n := []byte(`"hello"`) + iptr := &n + i := JSONFromPtr(iptr) + assertJSON(t, i, "JSONFromPtr()") + + null := JSONFromPtr(nil) + assertNullJSON(t, null, "JSONFromPtr(nil)") +} + +func TestUnmarshalJSON(t *testing.T) { + var i JSON + err := json.Unmarshal(bytesJSON, &i) + maybePanic(err) + assertJSON(t, i, "[]byte json") + + var ni JSON + err = ni.UnmarshalJSON([]byte{}) + if ni.Valid == true { + t.Errorf("expected Valid to be false, got true") + } + if !bytes.Equal(ni.JSON, []byte("null")) { + t.Errorf("Expected JSON to be nil slice, but was not: %#v %#v", ni.JSON, []byte(`null`)) + } + + var null JSON + err = null.UnmarshalJSON(nil) + if null.Valid == true { + t.Errorf("expected Valid to be false, got true") + } + if !bytes.Equal(null.JSON, []byte(`null`)) { + t.Errorf("Expected JSON to be []byte nil, but was not: %#v %#v", null.JSON, []byte(`null`)) + } +} + +func TestTextUnmarshalJSON(t *testing.T) { + var i JSON + err := i.UnmarshalText([]byte(`"hello"`)) + maybePanic(err) + assertJSON(t, i, "UnmarshalText() []byte") + + var blank JSON + err = blank.UnmarshalText([]byte("")) + maybePanic(err) + assertNullJSON(t, blank, "UnmarshalText() empty []byte") +} + +func TestMarshalJSON(t *testing.T) { + i := JSONFrom([]byte(`"hello"`)) + data, err := json.Marshal(i) + maybePanic(err) + assertJSONEquals(t, data, `"hello"`, "non-empty json marshal") + + // invalid values should be encoded as null + null := NewJSON(nil, false) + data, err = json.Marshal(null) + maybePanic(err) + assertJSONEquals(t, data, "null", "null json marshal") +} + +func TestMarshalJSONText(t *testing.T) { + i := JSONFrom([]byte(`"hello"`)) + data, err := i.MarshalText() + maybePanic(err) + assertJSONEquals(t, data, `"hello"`, "non-empty text marshal") + + // invalid values should be encoded as null + null := NewJSON(nil, false) + data, err = null.MarshalText() + maybePanic(err) + assertJSONEquals(t, data, "", "null text marshal") +} + +func TestJSONPointer(t *testing.T) { + i := JSONFrom([]byte(`"hello"`)) + ptr := i.Ptr() + if !bytes.Equal(*ptr, []byte(`"hello"`)) { + t.Errorf("bad %s []byte: %#v ≠ %s\n", "pointer", ptr, `"hello"`) + } + + null := NewJSON(nil, false) + ptr = null.Ptr() + if ptr != nil { + t.Errorf("bad %s []byte: %#v ≠ %s\n", "nil pointer", ptr, "nil") + } +} + +func TestJSONIsZero(t *testing.T) { + i := JSONFrom([]byte(`"hello"`)) + if i.IsZero() { + t.Errorf("IsZero() should be false") + } + + null := NewJSON(nil, false) + if !null.IsZero() { + t.Errorf("IsZero() should be true") + } + + zero := NewJSON(nil, true) + if !zero.IsZero() { + t.Errorf("IsZero() should be true") + } + + nz := NewJSON([]byte("thing"), true) + if nz.IsZero() { + t.Error("IsZero() should be false") + } +} + +func TestJSONSetValid(t *testing.T) { + change := NewJSON(nil, false) + assertNullJSON(t, change, "SetValid()") + change.SetValid([]byte(`"hello"`)) + assertJSON(t, change, "SetValid()") +} + +func TestJSONScan(t *testing.T) { + var i JSON + err := i.Scan(`"hello"`) + maybePanic(err) + assertJSON(t, i, "scanned []byte") + + var null JSON + err = null.Scan(nil) + maybePanic(err) + assertNullJSON(t, null, "scanned null") +} + +func assertJSON(t *testing.T, i JSON, from string) { + if !bytes.Equal(i.JSON, []byte(`"hello"`)) { + t.Errorf("bad %s []byte: %#v ≠ %#v\n", from, string(i.JSON), string([]byte(`"hello"`))) + } + if !i.Valid { + t.Error(from, "is invalid, but should be valid") + } +} + +func assertNullJSON(t *testing.T, i JSON, from string) { + if i.Valid { + t.Error(from, "is valid, but should be invalid") + } +}