Skip to content

Commit

Permalink
feat: add Attribute type to model with value typing for query building
Browse files Browse the repository at this point in the history
Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
  • Loading branch information
jpower432 committed Aug 16, 2022
1 parent d16d41f commit 016e4b3
Show file tree
Hide file tree
Showing 41 changed files with 861 additions and 509 deletions.
113 changes: 57 additions & 56 deletions attributes/attributes.go
Original file line number Diff line number Diff line change
@@ -1,88 +1,89 @@
package attributes

import (
"fmt"
"sort"
"strings"

"encoding/json"
"errors"
"github.com/uor-framework/uor-client-go/model"
)

// Attributes implements the model.Attributes interface
// using a multi-map storing a set of values.
// The current implementation would allow for aggregation of the attributes
// of child nodes to the parent nodes.
type Attributes map[string]map[string]struct{}
var ErrWrongKind = errors.New("wrong value kind")

// Attributes implements the model.Attributes interface.
type Attributes map[string]model.Attribute

var _ model.Attributes = &Attributes{}
var _ model.AttributeSet = &Attributes{}

// Find returns all values stored for a specified key.
func (a Attributes) Find(key string) []string {
valSet, exists := a[key]
func (a Attributes) Find(key string) model.Attribute {
val, exists := a[key]
if !exists {
return nil
}
var vals []string
for val := range valSet {
vals = append(vals, val)
}
return vals
return val
}

// Exists returns whether a key,value pair exists in the
// attribute set.
func (a Attributes) Exists(key, value string) bool {
vals, exists := a[key]
if !exists {
func (a Attributes) Exists(key string, kind model.Kind, value interface{}) bool {
val, ok := a[key]
if !ok {
return false
}
_, valExists := vals[value]
return valExists
}

// Strings returns a string representation of the
// attribute set.
func (a Attributes) String() string {
out := new(strings.Builder)
keys := make([]string, 0, len(a))
for k := range a {
keys = append(keys, k)
if val.Kind() != kind {
return false
}
sort.Strings(keys)
for _, key := range keys {
vals := a.List()[key]
sort.Strings(vals)
for _, val := range vals {
line := fmt.Sprintf("%s=%s,", key, val)
out.WriteString(line)

switch kind {
case model.KindString:
s, err := val.AsString()
if err != nil {
return false
}
return s == value.(string)
case model.KindNumber:
n, err := val.AsNumber()
if err != nil {
return false
}
return n == value.(float64)
case model.KindBool:
b, err := val.AsBool()
if err != nil {
return false
}
return b == value.(bool)
case model.KindNull:
if val.IsNull() {
return true
}
fallthrough
default:
return false
}
return strings.TrimSuffix(out.String(), ",")
}

// AsJSON returns a JSON formatted string representation of the
// attribute set. If the values are not valid, nil is returned.
func (a Attributes) AsJSON() json.RawMessage {
j := map[string]interface{}{}
for key, value := range a {
j[key] = value.AsAny()
}
jsonBytes, err := json.Marshal(j)
if err != nil {
return nil
}
return jsonBytes
}

// List will list all key, value pairs for the attributes in a
// consumable format.
func (a Attributes) List() map[string][]string {
list := make(map[string][]string, len(a))
for key, vals := range a {
for val := range vals {
list[key] = append(list[key], val)
}
}
return list
func (a Attributes) List() map[string]model.Attribute {
return a
}

// Len returns the length of the attribute set.
func (a Attributes) Len() int {
return len(a)
}

// Merge will merge the input Attributes with the receiver.
func (a Attributes) Merge(attr model.Attributes) {
for key, vals := range attr.List() {
for _, val := range vals {
sub := a[key]
sub[val] = struct{}{}
}
}
}
77 changes: 40 additions & 37 deletions attributes/attributes_test.go
Original file line number Diff line number Diff line change
@@ -1,51 +1,54 @@
package attributes

import (
"testing"

"github.com/stretchr/testify/require"
"github.com/uor-framework/uor-client-go/model"
"testing"
)

func TestExists(t *testing.T) {
attributes := Attributes{
"kind": map[string]struct{}{
"jpg": {},
"txt": {},
},
"name": map[string]struct{}{
"fish.jpg": {},
},
func TestAttributes_AsJSON(t *testing.T) {
expString := `{"name":"test","size":2}`
test := Attributes{
"name": NewString("name", "test"),
"size": NewNumber("size", 2),
}
require.Equal(t, expString, string(test.AsJSON()))
}

func TestAttributes_Exists(t *testing.T) {
test := Attributes{
"name": NewString("name", "test"),
"size": NewNumber("size", 2),
}
require.True(t, test.Exists("name", model.KindString, "test"))
}

func TestAttributes_Find(t *testing.T) {
test := Attributes{
"name": NewString("name", "test"),
"size": NewNumber("size", 2),
}
require.True(t, attributes.Exists("kind", "jpg"))
require.False(t, attributes.Exists("kind", "png"))
val := test.Find("name")
require.Equal(t, "name", val.Key())
require.Equal(t, model.KindString, val.Kind())
s, err := val.AsString()
require.NoError(t, err)
require.Equal(t, "test", s)
}

func TestFind(t *testing.T) {
attributes := Attributes{
"kind": map[string]struct{}{
"jpg": {},
"txt": {},
},
"name": map[string]struct{}{
"fish.jpg": {},
},
func TestAttributes_Len(t *testing.T) {
test := Attributes{
"name": NewString("name", "test"),
"size": NewNumber("size", 2),
}
result := attributes.Find("kind")
require.Len(t, result, 2)
require.Contains(t, result, "jpg")
require.Contains(t, result, "txt")
require.Equal(t, 2, test.Len())
}

func TestAttributes_String(t *testing.T) {
expString := `kind=jpg,kind=txt,name=fish.jpg`
attributes := Attributes{
"kind": map[string]struct{}{
"jpg": {},
"txt": {},
},
"name": map[string]struct{}{
"fish.jpg": {},
},
func TestAttributes_List(t *testing.T) {
test := Attributes{
"name": NewString("name", "test"),
"size": NewNumber("size", 2),
}
require.Equal(t, expString, attributes.String())
list := test.List()
require.Len(t, list, 2)
}
55 changes: 55 additions & 0 deletions attributes/bool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package attributes

import (
"github.com/uor-framework/uor-client-go/model"
)

type boolAttribute struct {
key string
value bool
}

var _ model.Attribute = boolAttribute{}

// NewBool returns a boolean attribute.
func NewBool(key string, value bool) model.Attribute {
return boolAttribute{key: key, value: value}
}

// Kind returns the kind for the attribute.
func (a boolAttribute) Kind() model.Kind {
return model.KindBool
}

// Key return the attribute key.
func (a boolAttribute) Key() string {
return a.key
}

// IsNull returns whether the value is null.
func (a boolAttribute) IsNull() bool {
return false
}

// AsBool returns the value as a boolean errors if that is not
// the underlying type.
func (a boolAttribute) AsBool() (bool, error) {
return a.value, nil
}

// AsString returns the value as a string errors if that is not
// the underlying type.
func (a boolAttribute) AsString() (string, error) {
return "", ErrWrongKind
}

// AsNumber returns the value as a number value errors if that is not
// the underlying type.
func (a boolAttribute) AsNumber() (float64, error) {
return 0, ErrWrongKind
}

// AsAny returns the value as an interface.
func (a boolAttribute) AsAny() interface{} {
return a.value
}
33 changes: 33 additions & 0 deletions attributes/bool_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package attributes

import (
"github.com/stretchr/testify/require"
"github.com/uor-framework/uor-client-go/model"
"testing"
)

func TestBoolAttribute_Kind(t *testing.T) {
test := NewBool("test", true)
require.Equal(t, model.KindBool, test.Kind())
}

func TestBoolAttribute_AsBool(t *testing.T) {
test := NewBool("test", true)
b, err := test.AsBool()
require.NoError(t, err)
require.Equal(t, true, b)
}

func TestBoolAttribute_AsNumber(t *testing.T) {
test := NewBool("test", false)
b, err := test.AsNumber()
require.ErrorIs(t, ErrWrongKind, err)
require.Equal(t, float64(0), b)
}

func TestBoolAttribute_AsString(t *testing.T) {
test := NewBool("test", false)
b, err := test.AsString()
require.ErrorIs(t, ErrWrongKind, err)
require.Equal(t, "", b)
}
Loading

0 comments on commit 016e4b3

Please sign in to comment.