-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add Attribute type to model with value typing for query building
Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
- Loading branch information
Showing
41 changed files
with
861 additions
and
509 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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{}{} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
Oops, something went wrong.