diff --git a/client/go/internal/vespa/slime/array.go b/client/go/internal/vespa/slime/array.go new file mode 100644 index 000000000000..371c2340bc0e --- /dev/null +++ b/client/go/internal/vespa/slime/array.go @@ -0,0 +1,29 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package slime + +type arrayValue struct { + emptyValue + value []Value +} + +func Array() *arrayValue { return &arrayValue{} } +func (*arrayValue) Type() Type { return ARRAY } + +func (arr *arrayValue) NumEntries() int { return len(arr.value) } +func (arr *arrayValue) Entry(index int) Value { + if index < len(arr.value) { + return arr.value[index] + } + return Invalid +} +func (arr *arrayValue) EachEntry(f func(index int, value Value)) { + for i, x := range arr.value { + f(i, x) + } +} + +func (arr *arrayValue) Add(value Value) Value { + arr.value = append(arr.value, value) + return value +} diff --git a/client/go/internal/vespa/slime/array_test.go b/client/go/internal/vespa/slime/array_test.go new file mode 100644 index 000000000000..c8006badac19 --- /dev/null +++ b/client/go/internal/vespa/slime/array_test.go @@ -0,0 +1,42 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package slime + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestArray(t *testing.T) { + arr := Array() + actual := []Value{ + arr.Add(Empty), + arr.Add(Bool(true)), + arr.Add(Long(5)), + arr.Add(Double(5.5)), + arr.Add(String("foo")), + arr.Add(Data([]byte{1, 2, 3}))} + + expect := []expectLeaf{ + expectLeaf{mytype: EMPTY}, + expectLeaf{mytype: BOOL, boolVal: true}, + expectLeaf{mytype: LONG, longVal: 5, doubleVal: 5}, + expectLeaf{mytype: DOUBLE, longVal: 5, doubleVal: 5.5}, + expectLeaf{mytype: STRING, stringVal: "foo"}, + expectLeaf{mytype: DATA, dataVal: []byte{1, 2, 3}}} + + var expectIndex int + var collect []Value + arr.EachEntry(func(idx int, val Value) { + assert.Equal(t, idx, expectIndex) + collect = append(collect, val) + expectIndex++ + }) + + assert.Equal(t, arr.NumEntries(), len(expect)) + for i, e := range expect { + checkLeaf(t, actual[i], e) + checkLeaf(t, collect[i], e) + checkLeaf(t, arr.Entry(i), e) + } +} diff --git a/client/go/internal/vespa/slime/leaf.go b/client/go/internal/vespa/slime/leaf.go new file mode 100644 index 000000000000..d2e6c8399f90 --- /dev/null +++ b/client/go/internal/vespa/slime/leaf.go @@ -0,0 +1,50 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package slime + +type boolValue struct { + emptyValue + value bool +} + +func Bool(v bool) Value { return &boolValue{value: v} } +func (*boolValue) Type() Type { return BOOL } +func (v *boolValue) AsBool() bool { return v.value } + +type longValue struct { + emptyValue + value int64 +} + +func Long(v int64) Value { return &longValue{value: v} } +func (*longValue) Type() Type { return LONG } +func (v *longValue) AsLong() int64 { return v.value } +func (v *longValue) AsDouble() float64 { return float64(v.value) } + +type doubleValue struct { + emptyValue + value float64 +} + +func Double(v float64) Value { return &doubleValue{value: v} } +func (*doubleValue) Type() Type { return DOUBLE } +func (v *doubleValue) AsLong() int64 { return int64(v.value) } +func (v *doubleValue) AsDouble() float64 { return v.value } + +type stringValue struct { + emptyValue + value string +} + +func String(v string) Value { return &stringValue{value: v} } +func (*stringValue) Type() Type { return STRING } +func (v *stringValue) AsString() string { return v.value } + +type dataValue struct { + emptyValue + value []byte +} + +func Data(v []byte) Value { return &dataValue{value: v} } +func (*dataValue) Type() Type { return DATA } +func (v *dataValue) AsData() []byte { return v.value } diff --git a/client/go/internal/vespa/slime/leaf_test.go b/client/go/internal/vespa/slime/leaf_test.go new file mode 100644 index 000000000000..501af06c6620 --- /dev/null +++ b/client/go/internal/vespa/slime/leaf_test.go @@ -0,0 +1,65 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package slime + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +type expectLeaf struct { + invalid bool + mytype Type + boolVal bool + longVal int64 + doubleVal float64 + stringVal string + dataVal []byte +} + +func checkLeaf(t *testing.T, value Value, expect expectLeaf) { + if expect.dataVal == nil { + expect.dataVal = emptyBytes + } + assert.Equal(t, value.Valid(), !expect.invalid) + assert.Equal(t, value.Type(), expect.mytype) + assert.Equal(t, value.AsBool(), expect.boolVal) + assert.Equal(t, value.AsLong(), expect.longVal) + assert.Equal(t, value.AsDouble(), expect.doubleVal) + assert.Equal(t, value.AsString(), expect.stringVal) + assert.Equal(t, value.AsData(), expect.dataVal) +} + +func TestEmpty(t *testing.T) { + checkLeaf(t, Empty, expectLeaf{}) + checkLeaf(t, Invalid, expectLeaf{invalid: true}) +} + +func TestBool(t *testing.T) { + checkLeaf(t, Bool(false), expectLeaf{mytype: BOOL}) + checkLeaf(t, Bool(true), expectLeaf{mytype: BOOL, boolVal: true}) +} + +func TestLong(t *testing.T) { + checkLeaf(t, Long(0), expectLeaf{mytype: LONG}) + checkLeaf(t, Long(5), expectLeaf{mytype: LONG, longVal: 5, doubleVal: 5}) + checkLeaf(t, Long(7), expectLeaf{mytype: LONG, longVal: 7, doubleVal: 7}) +} + +func TestDouble(t *testing.T) { + checkLeaf(t, Double(0.0), expectLeaf{mytype: DOUBLE}) + checkLeaf(t, Double(5.0), expectLeaf{mytype: DOUBLE, longVal: 5, doubleVal: 5.0}) + checkLeaf(t, Double(7.5), expectLeaf{mytype: DOUBLE, longVal: 7, doubleVal: 7.5}) +} + +func TestString(t *testing.T) { + checkLeaf(t, String(""), expectLeaf{mytype: STRING}) + checkLeaf(t, String("foo"), expectLeaf{mytype: STRING, stringVal: "foo"}) + checkLeaf(t, String("bar"), expectLeaf{mytype: STRING, stringVal: "bar"}) +} + +func TestData(t *testing.T) { + checkLeaf(t, Data(emptyBytes), expectLeaf{mytype: DATA}) + checkLeaf(t, Data([]byte{1, 2, 3}), expectLeaf{mytype: DATA, dataVal: []byte{1, 2, 3}}) + checkLeaf(t, Data([]byte{5, 6}), expectLeaf{mytype: DATA, dataVal: []byte{5, 6}}) +} diff --git a/client/go/internal/vespa/slime/object.go b/client/go/internal/vespa/slime/object.go new file mode 100644 index 000000000000..c1c8610a01fe --- /dev/null +++ b/client/go/internal/vespa/slime/object.go @@ -0,0 +1,34 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package slime + +type objectValue struct { + emptyValue + value map[string]Value +} + +func Object() Value { return &objectValue{value: make(map[string]Value)} } +func (*objectValue) Type() Type { return OBJECT } + +func (obj *objectValue) NumFields() int { return len(obj.value) } +func (obj *objectValue) Field(name string) Value { + value, found := obj.value[name] + if found { + return value + } + return Invalid +} +func (obj *objectValue) EachField(f func(name string, value Value)) { + for n, x := range obj.value { + f(n, x) + } +} + +func (obj *objectValue) Set(name string, value Value) Value { + _, found := obj.value[name] + if found { + return Invalid + } + obj.value[name] = value + return value +} diff --git a/client/go/internal/vespa/slime/object_test.go b/client/go/internal/vespa/slime/object_test.go new file mode 100644 index 000000000000..8beea3b00d8e --- /dev/null +++ b/client/go/internal/vespa/slime/object_test.go @@ -0,0 +1,40 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package slime + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestObject(t *testing.T) { + obj := Object() + actual := map[string]Value{ + "a": obj.Set("a", Empty), + "b": obj.Set("b", Bool(true)), + "c": obj.Set("c", Long(5)), + "d": obj.Set("d", Double(5.5)), + "e": obj.Set("e", String("foo")), + "f": obj.Set("f", Data([]byte{1, 2, 3}))} + + expect := map[string]expectLeaf{ + "a": expectLeaf{mytype: EMPTY}, + "b": expectLeaf{mytype: BOOL, boolVal: true}, + "c": expectLeaf{mytype: LONG, longVal: 5, doubleVal: 5}, + "d": expectLeaf{mytype: DOUBLE, longVal: 5, doubleVal: 5.5}, + "e": expectLeaf{mytype: STRING, stringVal: "foo"}, + "f": expectLeaf{mytype: DATA, dataVal: []byte{1, 2, 3}}} + + collect := make(map[string]Value) + obj.EachField(func(name string, val Value) { + collect[name] = val + }) + + assert.Equal(t, obj.NumFields(), len(expect)) + assert.Equal(t, len(collect), len(expect)) + for n, e := range expect { + checkLeaf(t, actual[n], e) + checkLeaf(t, collect[n], e) + checkLeaf(t, obj.Field(n), e) + } +} diff --git a/client/go/internal/vespa/slime/type.go b/client/go/internal/vespa/slime/type.go new file mode 100644 index 000000000000..fdead48dd85e --- /dev/null +++ b/client/go/internal/vespa/slime/type.go @@ -0,0 +1,16 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package slime + +type Type byte + +const ( + EMPTY Type = iota + BOOL + LONG + DOUBLE + STRING + DATA + ARRAY + OBJECT +) diff --git a/client/go/internal/vespa/slime/value.go b/client/go/internal/vespa/slime/value.go new file mode 100644 index 000000000000..f48143f4fc82 --- /dev/null +++ b/client/go/internal/vespa/slime/value.go @@ -0,0 +1,45 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package slime + +var ( + emptyBytes []byte = make([]byte, 0) + Empty Value = &emptyValue{} + Invalid Value = (*emptyValue)(nil) +) + +type Value interface { + Valid() bool + Type() Type + AsBool() bool + AsLong() int64 + AsDouble() float64 + AsString() string + AsData() []byte + NumEntries() int + Entry(index int) Value + EachEntry(func(index int, value Value)) + NumFields() int + Field(name string) Value + EachField(func(name string, value Value)) + Add(value Value) Value + Set(name string, value Value) Value +} + +type emptyValue struct{} + +func (v *emptyValue) Valid() bool { return (v != nil) } +func (*emptyValue) Type() Type { return EMPTY } +func (*emptyValue) AsBool() bool { return false } +func (*emptyValue) AsLong() int64 { return 0 } +func (*emptyValue) AsDouble() float64 { return 0 } +func (*emptyValue) AsString() string { return "" } +func (*emptyValue) AsData() []byte { return emptyBytes } +func (*emptyValue) NumEntries() int { return 0 } +func (*emptyValue) Entry(index int) Value { return Invalid } +func (*emptyValue) EachEntry(func(index int, value Value)) {} +func (*emptyValue) NumFields() int { return 0 } +func (*emptyValue) Field(name string) Value { return Invalid } +func (*emptyValue) EachField(func(name string, value Value)) {} +func (*emptyValue) Add(value Value) Value { return Invalid } +func (*emptyValue) Set(name string, value Value) Value { return Invalid }