mirror of https://github.com/k3s-io/k3s
unit test for cadvisor
parent
b66a822166
commit
3080262f12
|
@ -15,8 +15,9 @@ import (
|
|||
)
|
||||
|
||||
type fakeKubelet struct {
|
||||
infoFunc func(name string) (string, error)
|
||||
idFunc func(name string) (string, bool, error)
|
||||
infoFunc func(name string) (string, error)
|
||||
idFunc func(name string) (string, bool, error)
|
||||
statsFunc func(name string) (*api.ContainerStats, error)
|
||||
}
|
||||
|
||||
func (fk *fakeKubelet) GetContainerInfo(name string) (string, error) {
|
||||
|
@ -27,6 +28,10 @@ func (fk *fakeKubelet) GetContainerID(name string) (string, bool, error) {
|
|||
return fk.idFunc(name)
|
||||
}
|
||||
|
||||
func (fk *fakeKubelet) GetContainerStats(name string) (*api.ContainerStats, error) {
|
||||
return fk.statsFunc(name)
|
||||
}
|
||||
|
||||
// If we made everything distribute a list of ContainerManifests, we could just use
|
||||
// channelReader.
|
||||
type channelReaderSingle struct {
|
||||
|
|
|
@ -30,6 +30,8 @@ import (
|
|||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
"github.com/coreos/go-etcd/etcd"
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
"github.com/google/cadvisor/info"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// TODO: This doesn't reduce typing enough to make it worth the less readable errors. Remove.
|
||||
|
@ -911,3 +913,96 @@ func TestWatchEtcd(t *testing.T) {
|
|||
t.Errorf("Unexpected manifest(s) %#v %#v", read[0], manifest)
|
||||
}
|
||||
}
|
||||
|
||||
type mockCadvisorClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (self *mockCadvisorClient) ContainerInfo(name string) (*info.ContainerInfo, error) {
|
||||
args := self.Called(name)
|
||||
return args.Get(0).(*info.ContainerInfo), args.Error(1)
|
||||
}
|
||||
|
||||
func (self *mockCadvisorClient) MachineInfo() (*info.MachineInfo, error) {
|
||||
args := self.Called()
|
||||
return args.Get(0).(*info.MachineInfo), args.Error(1)
|
||||
}
|
||||
|
||||
func areSamePercentiles(
|
||||
cadvisorPercentiles []info.Percentile,
|
||||
kubePercentiles []api.Percentile,
|
||||
t *testing.T,
|
||||
) {
|
||||
if len(cadvisorPercentiles) != len(kubePercentiles) {
|
||||
t.Errorf("cadvisor gives %v percentiles; kubelet got %v", len(cadvisorPercentiles), len(kubePercentiles))
|
||||
return
|
||||
}
|
||||
for _, ap := range cadvisorPercentiles {
|
||||
found := false
|
||||
for _, kp := range kubePercentiles {
|
||||
if ap.Percentage == kp.Percentage {
|
||||
found = true
|
||||
if ap.Value != kp.Value {
|
||||
t.Errorf("%v percentile from cadvisor is %v; kubelet got %v",
|
||||
ap.Percentage,
|
||||
ap.Value,
|
||||
kp.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Unable to find %v percentile in kubelet's data", ap.Percentage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetContainerStats(t *testing.T) {
|
||||
containerId := "ab2cdf"
|
||||
containerPath := fmt.Sprintf("/docker/%v", containerId)
|
||||
containerInfo := &info.ContainerInfo{
|
||||
ContainerReference: info.ContainerReference{
|
||||
Name: containerPath,
|
||||
},
|
||||
StatsPercentiles: &info.ContainerStatsPercentiles{
|
||||
MaxMemoryUsage: 1024000,
|
||||
MemoryUsagePercentiles: []info.Percentile{
|
||||
info.Percentile{50, 100},
|
||||
info.Percentile{80, 180},
|
||||
info.Percentile{90, 190},
|
||||
},
|
||||
CpuUsagePercentiles: []info.Percentile{
|
||||
info.Percentile{51, 101},
|
||||
info.Percentile{81, 181},
|
||||
info.Percentile{91, 191},
|
||||
},
|
||||
},
|
||||
}
|
||||
fakeDocker := FakeDockerClient{
|
||||
err: nil,
|
||||
}
|
||||
|
||||
mockCadvisor := &mockCadvisorClient{}
|
||||
mockCadvisor.On("ContainerInfo", containerPath).Return(containerInfo, nil)
|
||||
|
||||
kubelet := Kubelet{
|
||||
DockerClient: &fakeDocker,
|
||||
CadvisorClient: mockCadvisor,
|
||||
}
|
||||
fakeDocker.containerList = []docker.APIContainers{
|
||||
{
|
||||
Names: []string{"foo"},
|
||||
ID: containerId,
|
||||
},
|
||||
}
|
||||
|
||||
stats, err := kubelet.GetContainerStats("foo")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if stats.MaxMemoryUsage != containerInfo.StatsPercentiles.MaxMemoryUsage {
|
||||
t.Errorf("wrong max memory usage")
|
||||
}
|
||||
areSamePercentiles(containerInfo.StatsPercentiles.CpuUsagePercentiles, stats.CpuUsagePercentiles, t)
|
||||
areSamePercentiles(containerInfo.StatsPercentiles.MemoryUsagePercentiles, stats.MemoryUsagePercentiles, t)
|
||||
mockCadvisor.AssertExpectations(t)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
objx - by Mat Ryer and Tyler Bunnell
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Stretchr, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,3 @@
|
|||
# objx
|
||||
|
||||
* Jump into the [API Documentation](http://godoc.org/github.com/stretchr/objx)
|
|
@ -0,0 +1,179 @@
|
|||
package objx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// arrayAccesRegexString is the regex used to extract the array number
|
||||
// from the access path
|
||||
const arrayAccesRegexString = `^(.+)\[([0-9]+)\]$`
|
||||
|
||||
// arrayAccesRegex is the compiled arrayAccesRegexString
|
||||
var arrayAccesRegex = regexp.MustCompile(arrayAccesRegexString)
|
||||
|
||||
// Get gets the value using the specified selector and
|
||||
// returns it inside a new Obj object.
|
||||
//
|
||||
// If it cannot find the value, Get will return a nil
|
||||
// value inside an instance of Obj.
|
||||
//
|
||||
// Get can only operate directly on map[string]interface{} and []interface.
|
||||
//
|
||||
// Example
|
||||
//
|
||||
// To access the title of the third chapter of the second book, do:
|
||||
//
|
||||
// o.Get("books[1].chapters[2].title")
|
||||
func (m Map) Get(selector string) *Value {
|
||||
rawObj := access(m, selector, nil, false, false)
|
||||
return &Value{data: rawObj}
|
||||
}
|
||||
|
||||
// Set sets the value using the specified selector and
|
||||
// returns the object on which Set was called.
|
||||
//
|
||||
// Set can only operate directly on map[string]interface{} and []interface
|
||||
//
|
||||
// Example
|
||||
//
|
||||
// To set the title of the third chapter of the second book, do:
|
||||
//
|
||||
// o.Set("books[1].chapters[2].title","Time to Go")
|
||||
func (m Map) Set(selector string, value interface{}) Map {
|
||||
access(m, selector, value, true, false)
|
||||
return m
|
||||
}
|
||||
|
||||
// access accesses the object using the selector and performs the
|
||||
// appropriate action.
|
||||
func access(current, selector, value interface{}, isSet, panics bool) interface{} {
|
||||
|
||||
switch selector.(type) {
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
||||
|
||||
if array, ok := current.([]interface{}); ok {
|
||||
index := intFromInterface(selector)
|
||||
|
||||
if index >= len(array) {
|
||||
if panics {
|
||||
panic(fmt.Sprintf("objx: Index %d is out of range. Slice only contains %d items.", index, len(array)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return array[index]
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
case string:
|
||||
|
||||
selStr := selector.(string)
|
||||
selSegs := strings.SplitN(selStr, PathSeparator, 2)
|
||||
thisSel := selSegs[0]
|
||||
index := -1
|
||||
var err error
|
||||
|
||||
// https://github.com/stretchr/objx/issues/12
|
||||
if strings.Contains(thisSel, "[") {
|
||||
|
||||
arrayMatches := arrayAccesRegex.FindStringSubmatch(thisSel)
|
||||
|
||||
if len(arrayMatches) > 0 {
|
||||
|
||||
// Get the key into the map
|
||||
thisSel = arrayMatches[1]
|
||||
|
||||
// Get the index into the array at the key
|
||||
index, err = strconv.Atoi(arrayMatches[2])
|
||||
|
||||
if err != nil {
|
||||
// This should never happen. If it does, something has gone
|
||||
// seriously wrong. Panic.
|
||||
panic("objx: Array index is not an integer. Must use array[int].")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if curMap, ok := current.(Map); ok {
|
||||
current = map[string]interface{}(curMap)
|
||||
}
|
||||
|
||||
// get the object in question
|
||||
switch current.(type) {
|
||||
case map[string]interface{}:
|
||||
curMSI := current.(map[string]interface{})
|
||||
if len(selSegs) <= 1 && isSet {
|
||||
curMSI[thisSel] = value
|
||||
return nil
|
||||
} else {
|
||||
current = curMSI[thisSel]
|
||||
}
|
||||
default:
|
||||
current = nil
|
||||
}
|
||||
|
||||
if current == nil && panics {
|
||||
panic(fmt.Sprintf("objx: '%v' invalid on object.", selector))
|
||||
}
|
||||
|
||||
// do we need to access the item of an array?
|
||||
if index > -1 {
|
||||
if array, ok := current.([]interface{}); ok {
|
||||
if index < len(array) {
|
||||
current = array[index]
|
||||
} else {
|
||||
if panics {
|
||||
panic(fmt.Sprintf("objx: Index %d is out of range. Slice only contains %d items.", index, len(array)))
|
||||
}
|
||||
current = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(selSegs) > 1 {
|
||||
current = access(current, selSegs[1], value, isSet, panics)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return current
|
||||
|
||||
}
|
||||
|
||||
// intFromInterface converts an interface object to the largest
|
||||
// representation of an unsigned integer using a type switch and
|
||||
// assertions
|
||||
func intFromInterface(selector interface{}) int {
|
||||
var value int
|
||||
switch selector.(type) {
|
||||
case int:
|
||||
value = selector.(int)
|
||||
case int8:
|
||||
value = int(selector.(int8))
|
||||
case int16:
|
||||
value = int(selector.(int16))
|
||||
case int32:
|
||||
value = int(selector.(int32))
|
||||
case int64:
|
||||
value = int(selector.(int64))
|
||||
case uint:
|
||||
value = int(selector.(uint))
|
||||
case uint8:
|
||||
value = int(selector.(uint8))
|
||||
case uint16:
|
||||
value = int(selector.(uint16))
|
||||
case uint32:
|
||||
value = int(selector.(uint32))
|
||||
case uint64:
|
||||
value = int(selector.(uint64))
|
||||
default:
|
||||
panic("objx: array access argument is not an integer type (this should never happen)")
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
package objx
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAccessorsAccessGetSingleField(t *testing.T) {
|
||||
|
||||
current := map[string]interface{}{"name": "Tyler"}
|
||||
assert.Equal(t, "Tyler", access(current, "name", nil, false, true))
|
||||
|
||||
}
|
||||
func TestAccessorsAccessGetDeep(t *testing.T) {
|
||||
|
||||
current := map[string]interface{}{"name": map[string]interface{}{"first": "Tyler", "last": "Bunnell"}}
|
||||
assert.Equal(t, "Tyler", access(current, "name.first", nil, false, true))
|
||||
assert.Equal(t, "Bunnell", access(current, "name.last", nil, false, true))
|
||||
|
||||
}
|
||||
func TestAccessorsAccessGetDeepDeep(t *testing.T) {
|
||||
|
||||
current := map[string]interface{}{"one": map[string]interface{}{"two": map[string]interface{}{"three": map[string]interface{}{"four": 4}}}}
|
||||
assert.Equal(t, 4, access(current, "one.two.three.four", nil, false, true))
|
||||
|
||||
}
|
||||
func TestAccessorsAccessGetInsideArray(t *testing.T) {
|
||||
|
||||
current := map[string]interface{}{"names": []interface{}{map[string]interface{}{"first": "Tyler", "last": "Bunnell"}, map[string]interface{}{"first": "Capitol", "last": "Bollocks"}}}
|
||||
assert.Equal(t, "Tyler", access(current, "names[0].first", nil, false, true))
|
||||
assert.Equal(t, "Bunnell", access(current, "names[0].last", nil, false, true))
|
||||
assert.Equal(t, "Capitol", access(current, "names[1].first", nil, false, true))
|
||||
assert.Equal(t, "Bollocks", access(current, "names[1].last", nil, false, true))
|
||||
|
||||
assert.Panics(t, func() {
|
||||
access(current, "names[2]", nil, false, true)
|
||||
})
|
||||
assert.Nil(t, access(current, "names[2]", nil, false, false))
|
||||
|
||||
}
|
||||
|
||||
func TestAccessorsAccessGetFromArrayWithInt(t *testing.T) {
|
||||
|
||||
current := []interface{}{map[string]interface{}{"first": "Tyler", "last": "Bunnell"}, map[string]interface{}{"first": "Capitol", "last": "Bollocks"}}
|
||||
one := access(current, 0, nil, false, false)
|
||||
two := access(current, 1, nil, false, false)
|
||||
three := access(current, 2, nil, false, false)
|
||||
|
||||
assert.Equal(t, "Tyler", one.(map[string]interface{})["first"])
|
||||
assert.Equal(t, "Capitol", two.(map[string]interface{})["first"])
|
||||
assert.Nil(t, three)
|
||||
|
||||
}
|
||||
|
||||
func TestAccessorsGet(t *testing.T) {
|
||||
|
||||
current := New(map[string]interface{}{"name": "Tyler"})
|
||||
assert.Equal(t, "Tyler", current.Get("name").data)
|
||||
|
||||
}
|
||||
|
||||
func TestAccessorsAccessSetSingleField(t *testing.T) {
|
||||
|
||||
current := map[string]interface{}{"name": "Tyler"}
|
||||
access(current, "name", "Mat", true, false)
|
||||
assert.Equal(t, current["name"], "Mat")
|
||||
|
||||
access(current, "age", 29, true, true)
|
||||
assert.Equal(t, current["age"], 29)
|
||||
|
||||
}
|
||||
|
||||
func TestAccessorsAccessSetSingleFieldNotExisting(t *testing.T) {
|
||||
|
||||
current := map[string]interface{}{}
|
||||
access(current, "name", "Mat", true, false)
|
||||
assert.Equal(t, current["name"], "Mat")
|
||||
|
||||
}
|
||||
|
||||
func TestAccessorsAccessSetDeep(t *testing.T) {
|
||||
|
||||
current := map[string]interface{}{"name": map[string]interface{}{"first": "Tyler", "last": "Bunnell"}}
|
||||
|
||||
access(current, "name.first", "Mat", true, true)
|
||||
access(current, "name.last", "Ryer", true, true)
|
||||
|
||||
assert.Equal(t, "Mat", access(current, "name.first", nil, false, true))
|
||||
assert.Equal(t, "Ryer", access(current, "name.last", nil, false, true))
|
||||
|
||||
}
|
||||
func TestAccessorsAccessSetDeepDeep(t *testing.T) {
|
||||
|
||||
current := map[string]interface{}{"one": map[string]interface{}{"two": map[string]interface{}{"three": map[string]interface{}{"four": 4}}}}
|
||||
|
||||
access(current, "one.two.three.four", 5, true, true)
|
||||
|
||||
assert.Equal(t, 5, access(current, "one.two.three.four", nil, false, true))
|
||||
|
||||
}
|
||||
func TestAccessorsAccessSetArray(t *testing.T) {
|
||||
|
||||
current := map[string]interface{}{"names": []interface{}{"Tyler"}}
|
||||
|
||||
access(current, "names[0]", "Mat", true, true)
|
||||
|
||||
assert.Equal(t, "Mat", access(current, "names[0]", nil, false, true))
|
||||
|
||||
}
|
||||
func TestAccessorsAccessSetInsideArray(t *testing.T) {
|
||||
|
||||
current := map[string]interface{}{"names": []interface{}{map[string]interface{}{"first": "Tyler", "last": "Bunnell"}, map[string]interface{}{"first": "Capitol", "last": "Bollocks"}}}
|
||||
|
||||
access(current, "names[0].first", "Mat", true, true)
|
||||
access(current, "names[0].last", "Ryer", true, true)
|
||||
access(current, "names[1].first", "Captain", true, true)
|
||||
access(current, "names[1].last", "Underpants", true, true)
|
||||
|
||||
assert.Equal(t, "Mat", access(current, "names[0].first", nil, false, true))
|
||||
assert.Equal(t, "Ryer", access(current, "names[0].last", nil, false, true))
|
||||
assert.Equal(t, "Captain", access(current, "names[1].first", nil, false, true))
|
||||
assert.Equal(t, "Underpants", access(current, "names[1].last", nil, false, true))
|
||||
|
||||
}
|
||||
|
||||
func TestAccessorsAccessSetFromArrayWithInt(t *testing.T) {
|
||||
|
||||
current := []interface{}{map[string]interface{}{"first": "Tyler", "last": "Bunnell"}, map[string]interface{}{"first": "Capitol", "last": "Bollocks"}}
|
||||
one := access(current, 0, nil, false, false)
|
||||
two := access(current, 1, nil, false, false)
|
||||
three := access(current, 2, nil, false, false)
|
||||
|
||||
assert.Equal(t, "Tyler", one.(map[string]interface{})["first"])
|
||||
assert.Equal(t, "Capitol", two.(map[string]interface{})["first"])
|
||||
assert.Nil(t, three)
|
||||
|
||||
}
|
||||
|
||||
func TestAccessorsSet(t *testing.T) {
|
||||
|
||||
current := New(map[string]interface{}{"name": "Tyler"})
|
||||
current.Set("name", "Mat")
|
||||
assert.Equal(t, "Mat", current.Get("name").data)
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
case []{1}:
|
||||
a := object.([]{1})
|
||||
if isSet {
|
||||
a[index] = value.({1})
|
||||
} else {
|
||||
if index >= len(a) {
|
||||
if panics {
|
||||
panic(fmt.Sprintf("objx: Index %d is out of range because the []{1} only contains %d items.", index, len(a)))
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return a[index]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>
|
||||
Codegen
|
||||
</title>
|
||||
<style>
|
||||
body {
|
||||
width: 800px;
|
||||
margin: auto;
|
||||
}
|
||||
textarea {
|
||||
width: 100%;
|
||||
min-height: 100px;
|
||||
font-family: Courier;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h2>
|
||||
Template
|
||||
</h2>
|
||||
<p>
|
||||
Use <code>{x}</code> as a placeholder for each argument.
|
||||
</p>
|
||||
<textarea id="template"></textarea>
|
||||
|
||||
<h2>
|
||||
Arguments (comma separated)
|
||||
</h2>
|
||||
<p>
|
||||
One block per line
|
||||
</p>
|
||||
<textarea id="args"></textarea>
|
||||
|
||||
<h2>
|
||||
Output
|
||||
</h2>
|
||||
<input id="go" type="button" value="Generate code" />
|
||||
|
||||
<textarea id="output"></textarea>
|
||||
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
|
||||
<script>
|
||||
|
||||
$(function(){
|
||||
|
||||
$("#go").click(function(){
|
||||
|
||||
var output = ""
|
||||
var template = $("#template").val()
|
||||
var args = $("#args").val()
|
||||
|
||||
// collect the args
|
||||
var argLines = args.split("\n")
|
||||
for (var line in argLines) {
|
||||
|
||||
var argLine = argLines[line];
|
||||
var thisTemp = template
|
||||
|
||||
// get individual args
|
||||
var args = argLine.split(",")
|
||||
|
||||
for (var argI in args) {
|
||||
var argText = args[argI];
|
||||
var argPlaceholder = "{" + argI + "}";
|
||||
|
||||
while (thisTemp.indexOf(argPlaceholder) > -1) {
|
||||
thisTemp = thisTemp.replace(argPlaceholder, argText);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
output += thisTemp
|
||||
|
||||
}
|
||||
|
||||
$("#output").val(output);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,286 @@
|
|||
/*
|
||||
{4} ({1} and []{1})
|
||||
--------------------------------------------------
|
||||
*/
|
||||
|
||||
// {4} gets the value as a {1}, returns the optionalDefault
|
||||
// value or a system default object if the value is the wrong type.
|
||||
func (v *Value) {4}(optionalDefault ...{1}) {1} {
|
||||
if s, ok := v.data.({1}); ok {
|
||||
return s
|
||||
}
|
||||
if len(optionalDefault) == 1 {
|
||||
return optionalDefault[0]
|
||||
}
|
||||
return {3}
|
||||
}
|
||||
|
||||
// Must{4} gets the value as a {1}.
|
||||
//
|
||||
// Panics if the object is not a {1}.
|
||||
func (v *Value) Must{4}() {1} {
|
||||
return v.data.({1})
|
||||
}
|
||||
|
||||
// {4}Slice gets the value as a []{1}, returns the optionalDefault
|
||||
// value or nil if the value is not a []{1}.
|
||||
func (v *Value) {4}Slice(optionalDefault ...[]{1}) []{1} {
|
||||
if s, ok := v.data.([]{1}); ok {
|
||||
return s
|
||||
}
|
||||
if len(optionalDefault) == 1 {
|
||||
return optionalDefault[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Must{4}Slice gets the value as a []{1}.
|
||||
//
|
||||
// Panics if the object is not a []{1}.
|
||||
func (v *Value) Must{4}Slice() []{1} {
|
||||
return v.data.([]{1})
|
||||
}
|
||||
|
||||
// Is{4} gets whether the object contained is a {1} or not.
|
||||
func (v *Value) Is{4}() bool {
|
||||
_, ok := v.data.({1})
|
||||
return ok
|
||||
}
|
||||
|
||||
// Is{4}Slice gets whether the object contained is a []{1} or not.
|
||||
func (v *Value) Is{4}Slice() bool {
|
||||
_, ok := v.data.([]{1})
|
||||
return ok
|
||||
}
|
||||
|
||||
// Each{4} calls the specified callback for each object
|
||||
// in the []{1}.
|
||||
//
|
||||
// Panics if the object is the wrong type.
|
||||
func (v *Value) Each{4}(callback func(int, {1}) bool) *Value {
|
||||
|
||||
for index, val := range v.Must{4}Slice() {
|
||||
carryon := callback(index, val)
|
||||
if carryon == false {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
// Where{4} uses the specified decider function to select items
|
||||
// from the []{1}. The object contained in the result will contain
|
||||
// only the selected items.
|
||||
func (v *Value) Where{4}(decider func(int, {1}) bool) *Value {
|
||||
|
||||
var selected []{1}
|
||||
|
||||
v.Each{4}(func(index int, val {1}) bool {
|
||||
shouldSelect := decider(index, val)
|
||||
if shouldSelect == false {
|
||||
selected = append(selected, val)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return &Value{data:selected}
|
||||
|
||||
}
|
||||
|
||||
// Group{4} uses the specified grouper function to group the items
|
||||
// keyed by the return of the grouper. The object contained in the
|
||||
// result will contain a map[string][]{1}.
|
||||
func (v *Value) Group{4}(grouper func(int, {1}) string) *Value {
|
||||
|
||||
groups := make(map[string][]{1})
|
||||
|
||||
v.Each{4}(func(index int, val {1}) bool {
|
||||
group := grouper(index, val)
|
||||
if _, ok := groups[group]; !ok {
|
||||
groups[group] = make([]{1}, 0)
|
||||
}
|
||||
groups[group] = append(groups[group], val)
|
||||
return true
|
||||
})
|
||||
|
||||
return &Value{data:groups}
|
||||
|
||||
}
|
||||
|
||||
// Replace{4} uses the specified function to replace each {1}s
|
||||
// by iterating each item. The data in the returned result will be a
|
||||
// []{1} containing the replaced items.
|
||||
func (v *Value) Replace{4}(replacer func(int, {1}) {1}) *Value {
|
||||
|
||||
arr := v.Must{4}Slice()
|
||||
replaced := make([]{1}, len(arr))
|
||||
|
||||
v.Each{4}(func(index int, val {1}) bool {
|
||||
replaced[index] = replacer(index, val)
|
||||
return true
|
||||
})
|
||||
|
||||
return &Value{data:replaced}
|
||||
|
||||
}
|
||||
|
||||
// Collect{4} uses the specified collector function to collect a value
|
||||
// for each of the {1}s in the slice. The data returned will be a
|
||||
// []interface{}.
|
||||
func (v *Value) Collect{4}(collector func(int, {1}) interface{}) *Value {
|
||||
|
||||
arr := v.Must{4}Slice()
|
||||
collected := make([]interface{}, len(arr))
|
||||
|
||||
v.Each{4}(func(index int, val {1}) bool {
|
||||
collected[index] = collector(index, val)
|
||||
return true
|
||||
})
|
||||
|
||||
return &Value{data:collected}
|
||||
}
|
||||
|
||||
// ************************************************************
|
||||
// TESTS
|
||||
// ************************************************************
|
||||
|
||||
func Test{4}(t *testing.T) {
|
||||
|
||||
val := {1}( {2} )
|
||||
m := map[string]interface{}{"value": val, "nothing": nil}
|
||||
assert.Equal(t, val, New(m).Get("value").{4}())
|
||||
assert.Equal(t, val, New(m).Get("value").Must{4}())
|
||||
assert.Equal(t, {1}({3}), New(m).Get("nothing").{4}())
|
||||
assert.Equal(t, val, New(m).Get("nothing").{4}({2}))
|
||||
|
||||
assert.Panics(t, func() {
|
||||
New(m).Get("age").Must{4}()
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func Test{4}Slice(t *testing.T) {
|
||||
|
||||
val := {1}( {2} )
|
||||
m := map[string]interface{}{"value": []{1}{ val }, "nothing": nil}
|
||||
assert.Equal(t, val, New(m).Get("value").{4}Slice()[0])
|
||||
assert.Equal(t, val, New(m).Get("value").Must{4}Slice()[0])
|
||||
assert.Equal(t, []{1}(nil), New(m).Get("nothing").{4}Slice())
|
||||
assert.Equal(t, val, New(m).Get("nothing").{4}Slice( []{1}{ {1}({2}) } )[0])
|
||||
|
||||
assert.Panics(t, func() {
|
||||
New(m).Get("nothing").Must{4}Slice()
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestIs{4}(t *testing.T) {
|
||||
|
||||
var v *Value
|
||||
|
||||
v = &Value{data: {1}({2})}
|
||||
assert.True(t, v.Is{4}())
|
||||
|
||||
v = &Value{data: []{1}{ {1}({2}) }}
|
||||
assert.True(t, v.Is{4}Slice())
|
||||
|
||||
}
|
||||
|
||||
func TestEach{4}(t *testing.T) {
|
||||
|
||||
v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }}
|
||||
count := 0
|
||||
replacedVals := make([]{1}, 0)
|
||||
assert.Equal(t, v, v.Each{4}(func(i int, val {1}) bool {
|
||||
|
||||
count++
|
||||
replacedVals = append(replacedVals, val)
|
||||
|
||||
// abort early
|
||||
if i == 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
}))
|
||||
|
||||
assert.Equal(t, count, 3)
|
||||
assert.Equal(t, replacedVals[0], v.Must{4}Slice()[0])
|
||||
assert.Equal(t, replacedVals[1], v.Must{4}Slice()[1])
|
||||
assert.Equal(t, replacedVals[2], v.Must{4}Slice()[2])
|
||||
|
||||
}
|
||||
|
||||
func TestWhere{4}(t *testing.T) {
|
||||
|
||||
v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }}
|
||||
|
||||
selected := v.Where{4}(func(i int, val {1}) bool {
|
||||
return i%2==0
|
||||
}).Must{4}Slice()
|
||||
|
||||
assert.Equal(t, 3, len(selected))
|
||||
|
||||
}
|
||||
|
||||
func TestGroup{4}(t *testing.T) {
|
||||
|
||||
v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }}
|
||||
|
||||
grouped := v.Group{4}(func(i int, val {1}) string {
|
||||
return fmt.Sprintf("%v", i%2==0)
|
||||
}).data.(map[string][]{1})
|
||||
|
||||
assert.Equal(t, 2, len(grouped))
|
||||
assert.Equal(t, 3, len(grouped["true"]))
|
||||
assert.Equal(t, 3, len(grouped["false"]))
|
||||
|
||||
}
|
||||
|
||||
func TestReplace{4}(t *testing.T) {
|
||||
|
||||
v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }}
|
||||
|
||||
rawArr := v.Must{4}Slice()
|
||||
|
||||
replaced := v.Replace{4}(func(index int, val {1}) {1} {
|
||||
if index < len(rawArr)-1 {
|
||||
return rawArr[index+1]
|
||||
}
|
||||
return rawArr[0]
|
||||
})
|
||||
|
||||
replacedArr := replaced.Must{4}Slice()
|
||||
if assert.Equal(t, 6, len(replacedArr)) {
|
||||
assert.Equal(t, replacedArr[0], rawArr[1])
|
||||
assert.Equal(t, replacedArr[1], rawArr[2])
|
||||
assert.Equal(t, replacedArr[2], rawArr[3])
|
||||
assert.Equal(t, replacedArr[3], rawArr[4])
|
||||
assert.Equal(t, replacedArr[4], rawArr[5])
|
||||
assert.Equal(t, replacedArr[5], rawArr[0])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestCollect{4}(t *testing.T) {
|
||||
|
||||
v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }}
|
||||
|
||||
collected := v.Collect{4}(func(index int, val {1}) interface{} {
|
||||
return index
|
||||
})
|
||||
|
||||
collectedArr := collected.MustInterSlice()
|
||||
if assert.Equal(t, 6, len(collectedArr)) {
|
||||
assert.Equal(t, collectedArr[0], 0)
|
||||
assert.Equal(t, collectedArr[1], 1)
|
||||
assert.Equal(t, collectedArr[2], 2)
|
||||
assert.Equal(t, collectedArr[3], 3)
|
||||
assert.Equal(t, collectedArr[4], 4)
|
||||
assert.Equal(t, collectedArr[5], 5)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
Interface,interface{},"something",nil,Inter
|
||||
Map,map[string]interface{},map[string]interface{}{"name":"Tyler"},nil,MSI
|
||||
ObjxMap,(Map),New(1),New(nil),ObjxMap
|
||||
Bool,bool,true,false,Bool
|
||||
String,string,"hello","",Str
|
||||
Int,int,1,0,Int
|
||||
Int8,int8,1,0,Int8
|
||||
Int16,int16,1,0,Int16
|
||||
Int32,int32,1,0,Int32
|
||||
Int64,int64,1,0,Int64
|
||||
Uint,uint,1,0,Uint
|
||||
Uint8,uint8,1,0,Uint8
|
||||
Uint16,uint16,1,0,Uint16
|
||||
Uint32,uint32,1,0,Uint32
|
||||
Uint64,uint64,1,0,Uint64
|
||||
Uintptr,uintptr,1,0,Uintptr
|
||||
Float32,float32,1,0,Float32
|
||||
Float64,float64,1,0,Float64
|
||||
Complex64,complex64,1,0,Complex64
|
||||
Complex128,complex128,1,0,Complex128
|
|
@ -0,0 +1,13 @@
|
|||
package objx
|
||||
|
||||
const (
|
||||
// PathSeparator is the character used to separate the elements
|
||||
// of the keypath.
|
||||
//
|
||||
// For example, `location.address.city`
|
||||
PathSeparator string = "."
|
||||
|
||||
// SignatureSeparator is the character that is used to
|
||||
// separate the Base64 string from the security signature.
|
||||
SignatureSeparator = "_"
|
||||
)
|
|
@ -0,0 +1,117 @@
|
|||
package objx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// JSON converts the contained object to a JSON string
|
||||
// representation
|
||||
func (m Map) JSON() (string, error) {
|
||||
|
||||
result, err := json.Marshal(m)
|
||||
|
||||
if err != nil {
|
||||
err = errors.New("objx: JSON encode failed with: " + err.Error())
|
||||
}
|
||||
|
||||
return string(result), err
|
||||
|
||||
}
|
||||
|
||||
// MustJSON converts the contained object to a JSON string
|
||||
// representation and panics if there is an error
|
||||
func (m Map) MustJSON() string {
|
||||
result, err := m.JSON()
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Base64 converts the contained object to a Base64 string
|
||||
// representation of the JSON string representation
|
||||
func (m Map) Base64() (string, error) {
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
jsonData, err := m.JSON()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
encoder := base64.NewEncoder(base64.StdEncoding, &buf)
|
||||
encoder.Write([]byte(jsonData))
|
||||
encoder.Close()
|
||||
|
||||
return buf.String(), nil
|
||||
|
||||
}
|
||||
|
||||
// MustBase64 converts the contained object to a Base64 string
|
||||
// representation of the JSON string representation and panics
|
||||
// if there is an error
|
||||
func (m Map) MustBase64() string {
|
||||
result, err := m.Base64()
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// SignedBase64 converts the contained object to a Base64 string
|
||||
// representation of the JSON string representation and signs it
|
||||
// using the provided key.
|
||||
func (m Map) SignedBase64(key string) (string, error) {
|
||||
|
||||
base64, err := m.Base64()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
sig := HashWithKey(base64, key)
|
||||
|
||||
return base64 + SignatureSeparator + sig, nil
|
||||
|
||||
}
|
||||
|
||||
// MustSignedBase64 converts the contained object to a Base64 string
|
||||
// representation of the JSON string representation and signs it
|
||||
// using the provided key and panics if there is an error
|
||||
func (m Map) MustSignedBase64(key string) string {
|
||||
result, err := m.SignedBase64(key)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/*
|
||||
URL Query
|
||||
------------------------------------------------
|
||||
*/
|
||||
|
||||
// URLValues creates a url.Values object from an Obj. This
|
||||
// function requires that the wrapped object be a map[string]interface{}
|
||||
func (m Map) URLValues() url.Values {
|
||||
|
||||
vals := make(url.Values)
|
||||
|
||||
for k, v := range m {
|
||||
//TODO: can this be done without sprintf?
|
||||
vals.Set(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return vals
|
||||
}
|
||||
|
||||
// URLQuery gets an encoded URL query representing the given
|
||||
// Obj. This function requires that the wrapped object be a
|
||||
// map[string]interface{}
|
||||
func (m Map) URLQuery() (string, error) {
|
||||
return m.URLValues().Encode(), nil
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
package objx
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConversionJSON(t *testing.T) {
|
||||
|
||||
jsonString := `{"name":"Mat"}`
|
||||
o := MustFromJSON(jsonString)
|
||||
|
||||
result, err := o.JSON()
|
||||
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, jsonString, result)
|
||||
}
|
||||
|
||||
assert.Equal(t, jsonString, o.MustJSON())
|
||||
|
||||
}
|
||||
|
||||
func TestConversionJSONWithError(t *testing.T) {
|
||||
|
||||
o := MSI()
|
||||
o["test"] = func() {}
|
||||
|
||||
assert.Panics(t, func() {
|
||||
o.MustJSON()
|
||||
})
|
||||
|
||||
_, err := o.JSON()
|
||||
|
||||
assert.Error(t, err)
|
||||
|
||||
}
|
||||
|
||||
func TestConversionBase64(t *testing.T) {
|
||||
|
||||
o := New(map[string]interface{}{"name": "Mat"})
|
||||
|
||||
result, err := o.Base64()
|
||||
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, "eyJuYW1lIjoiTWF0In0=", result)
|
||||
}
|
||||
|
||||
assert.Equal(t, "eyJuYW1lIjoiTWF0In0=", o.MustBase64())
|
||||
|
||||
}
|
||||
|
||||
func TestConversionBase64WithError(t *testing.T) {
|
||||
|
||||
o := MSI()
|
||||
o["test"] = func() {}
|
||||
|
||||
assert.Panics(t, func() {
|
||||
o.MustBase64()
|
||||
})
|
||||
|
||||
_, err := o.Base64()
|
||||
|
||||
assert.Error(t, err)
|
||||
|
||||
}
|
||||
|
||||
func TestConversionSignedBase64(t *testing.T) {
|
||||
|
||||
o := New(map[string]interface{}{"name": "Mat"})
|
||||
|
||||
result, err := o.SignedBase64("key")
|
||||
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, "eyJuYW1lIjoiTWF0In0=_67ee82916f90b2c0d68c903266e8998c9ef0c3d6", result)
|
||||
}
|
||||
|
||||
assert.Equal(t, "eyJuYW1lIjoiTWF0In0=_67ee82916f90b2c0d68c903266e8998c9ef0c3d6", o.MustSignedBase64("key"))
|
||||
|
||||
}
|
||||
|
||||
func TestConversionSignedBase64WithError(t *testing.T) {
|
||||
|
||||
o := MSI()
|
||||
o["test"] = func() {}
|
||||
|
||||
assert.Panics(t, func() {
|
||||
o.MustSignedBase64("key")
|
||||
})
|
||||
|
||||
_, err := o.SignedBase64("key")
|
||||
|
||||
assert.Error(t, err)
|
||||
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
// objx - Go package for dealing with maps, slices, JSON and other data.
|
||||
//
|
||||
// Overview
|
||||
//
|
||||
// Objx provides the `objx.Map` type, which is a `map[string]interface{}` that exposes
|
||||
// a powerful `Get` method (among others) that allows you to easily and quickly get
|
||||
// access to data within the map, without having to worry too much about type assertions,
|
||||
// missing data, default values etc.
|
||||
//
|
||||
// Pattern
|
||||
//
|
||||
// Objx uses a preditable pattern to make access data from within `map[string]interface{}'s
|
||||
// easy.
|
||||
//
|
||||
// Call one of the `objx.` functions to create your `objx.Map` to get going:
|
||||
//
|
||||
// m, err := objx.FromJSON(json)
|
||||
//
|
||||
// NOTE: Any methods or functions with the `Must` prefix will panic if something goes wrong,
|
||||
// the rest will be optimistic and try to figure things out without panicking.
|
||||
//
|
||||
// Use `Get` to access the value you're interested in. You can use dot and array
|
||||
// notation too:
|
||||
//
|
||||
// m.Get("places[0].latlng")
|
||||
//
|
||||
// Once you have saught the `Value` you're interested in, you can use the `Is*` methods
|
||||
// to determine its type.
|
||||
//
|
||||
// if m.Get("code").IsStr() { /* ... */ }
|
||||
//
|
||||
// Or you can just assume the type, and use one of the strong type methods to
|
||||
// extract the real value:
|
||||
//
|
||||
// m.Get("code").Int()
|
||||
//
|
||||
// If there's no value there (or if it's the wrong type) then a default value
|
||||
// will be returned, or you can be explicit about the default value.
|
||||
//
|
||||
// Get("code").Int(-1)
|
||||
//
|
||||
// If you're dealing with a slice of data as a value, Objx provides many useful
|
||||
// methods for iterating, manipulating and selecting that data. You can find out more
|
||||
// by exploring the index below.
|
||||
//
|
||||
// Reading data
|
||||
//
|
||||
// A simple example of how to use Objx:
|
||||
//
|
||||
// // use MustFromJSON to make an objx.Map from some JSON
|
||||
// m := objx.MustFromJSON(`{"name": "Mat", "age": 30}`)
|
||||
//
|
||||
// // get the details
|
||||
// name := m.Get("name").Str()
|
||||
// age := m.Get("age").Int()
|
||||
//
|
||||
// // get their nickname (or use their name if they
|
||||
// // don't have one)
|
||||
// nickname := m.Get("nickname").Str(name)
|
||||
//
|
||||
// Ranging
|
||||
//
|
||||
// Since `objx.Map` is a `map[string]interface{}` you can treat it as such. For
|
||||
// example, to `range` the data, do what you would expect:
|
||||
//
|
||||
// m := objx.MustFromJSON(json)
|
||||
// for key, value := range m {
|
||||
//
|
||||
// /* ... do your magic ... */
|
||||
//
|
||||
// }
|
||||
package objx
|
|
@ -0,0 +1,98 @@
|
|||
package objx
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var fixtures = []struct {
|
||||
// name is the name of the fixture (used for reporting
|
||||
// failures)
|
||||
name string
|
||||
// data is the JSON data to be worked on
|
||||
data string
|
||||
// get is the argument(s) to pass to Get
|
||||
get interface{}
|
||||
// output is the expected output
|
||||
output interface{}
|
||||
}{
|
||||
{
|
||||
name: "Simple get",
|
||||
data: `{"name": "Mat"}`,
|
||||
get: "name",
|
||||
output: "Mat",
|
||||
},
|
||||
{
|
||||
name: "Get with dot notation",
|
||||
data: `{"address": {"city": "Boulder"}}`,
|
||||
get: "address.city",
|
||||
output: "Boulder",
|
||||
},
|
||||
{
|
||||
name: "Deep get with dot notation",
|
||||
data: `{"one": {"two": {"three": {"four": "hello"}}}}`,
|
||||
get: "one.two.three.four",
|
||||
output: "hello",
|
||||
},
|
||||
{
|
||||
name: "Get missing with dot notation",
|
||||
data: `{"one": {"two": {"three": {"four": "hello"}}}}`,
|
||||
get: "one.ten",
|
||||
output: nil,
|
||||
},
|
||||
{
|
||||
name: "Get with array notation",
|
||||
data: `{"tags": ["one", "two", "three"]}`,
|
||||
get: "tags[1]",
|
||||
output: "two",
|
||||
},
|
||||
{
|
||||
name: "Get with array and dot notation",
|
||||
data: `{"types": { "tags": ["one", "two", "three"]}}`,
|
||||
get: "types.tags[1]",
|
||||
output: "two",
|
||||
},
|
||||
{
|
||||
name: "Get with array and dot notation - field after array",
|
||||
data: `{"tags": [{"name":"one"}, {"name":"two"}, {"name":"three"}]}`,
|
||||
get: "tags[1].name",
|
||||
output: "two",
|
||||
},
|
||||
{
|
||||
name: "Complex get with array and dot notation",
|
||||
data: `{"tags": [{"list": [{"one":"pizza"}]}]}`,
|
||||
get: "tags[0].list[0].one",
|
||||
output: "pizza",
|
||||
},
|
||||
{
|
||||
name: "Get field from within string should be nil",
|
||||
data: `{"name":"Tyler"}`,
|
||||
get: "name.something",
|
||||
output: nil,
|
||||
},
|
||||
{
|
||||
name: "Get field from within string (using array accessor) should be nil",
|
||||
data: `{"numbers":["one", "two", "three"]}`,
|
||||
get: "numbers[0].nope",
|
||||
output: nil,
|
||||
},
|
||||
}
|
||||
|
||||
func TestFixtures(t *testing.T) {
|
||||
|
||||
for _, fixture := range fixtures {
|
||||
|
||||
m := MustFromJSON(fixture.data)
|
||||
|
||||
// get the value
|
||||
t.Logf("Running get fixture: \"%s\" (%v)", fixture.name, fixture)
|
||||
value := m.Get(fixture.get.(string))
|
||||
|
||||
// make sure it matches
|
||||
assert.Equal(t, fixture.output, value.data,
|
||||
"Get fixture \"%s\" failed: %v", fixture.name, fixture,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
package objx
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MSIConvertable is an interface that defines methods for converting your
|
||||
// custom types to a map[string]interface{} representation.
|
||||
type MSIConvertable interface {
|
||||
// MSI gets a map[string]interface{} (msi) representing the
|
||||
// object.
|
||||
MSI() map[string]interface{}
|
||||
}
|
||||
|
||||
// Map provides extended functionality for working with
|
||||
// untyped data, in particular map[string]interface (msi).
|
||||
type Map map[string]interface{}
|
||||
|
||||
// Value returns the internal value instance
|
||||
func (m Map) Value() *Value {
|
||||
return &Value{data: m}
|
||||
}
|
||||
|
||||
// Nil represents a nil Map.
|
||||
var Nil Map = New(nil)
|
||||
|
||||
// New creates a new Map containing the map[string]interface{} in the data argument.
|
||||
// If the data argument is not a map[string]interface, New attempts to call the
|
||||
// MSI() method on the MSIConvertable interface to create one.
|
||||
func New(data interface{}) Map {
|
||||
if _, ok := data.(map[string]interface{}); !ok {
|
||||
if converter, ok := data.(MSIConvertable); ok {
|
||||
data = converter.MSI()
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return Map(data.(map[string]interface{}))
|
||||
}
|
||||
|
||||
// MSI creates a map[string]interface{} and puts it inside a new Map.
|
||||
//
|
||||
// The arguments follow a key, value pattern.
|
||||
//
|
||||
// Panics
|
||||
//
|
||||
// Panics if any key arugment is non-string or if there are an odd number of arguments.
|
||||
//
|
||||
// Example
|
||||
//
|
||||
// To easily create Maps:
|
||||
//
|
||||
// m := objx.MSI("name", "Mat", "age", 29, "subobj", objx.MSI("active", true))
|
||||
//
|
||||
// // creates an Map equivalent to
|
||||
// m := objx.New(map[string]interface{}{"name": "Mat", "age": 29, "subobj": map[string]interface{}{"active": true}})
|
||||
func MSI(keyAndValuePairs ...interface{}) Map {
|
||||
|
||||
newMap := make(map[string]interface{})
|
||||
keyAndValuePairsLen := len(keyAndValuePairs)
|
||||
|
||||
if keyAndValuePairsLen%2 != 0 {
|
||||
panic("objx: MSI must have an even number of arguments following the 'key, value' pattern.")
|
||||
}
|
||||
|
||||
for i := 0; i < keyAndValuePairsLen; i = i + 2 {
|
||||
|
||||
key := keyAndValuePairs[i]
|
||||
value := keyAndValuePairs[i+1]
|
||||
|
||||
// make sure the key is a string
|
||||
keyString, keyStringOK := key.(string)
|
||||
if !keyStringOK {
|
||||
panic("objx: MSI must follow 'string, interface{}' pattern. " + keyString + " is not a valid key.")
|
||||
}
|
||||
|
||||
newMap[keyString] = value
|
||||
|
||||
}
|
||||
|
||||
return New(newMap)
|
||||
}
|
||||
|
||||
// ****** Conversion Constructors
|
||||
|
||||
// MustFromJSON creates a new Map containing the data specified in the
|
||||
// jsonString.
|
||||
//
|
||||
// Panics if the JSON is invalid.
|
||||
func MustFromJSON(jsonString string) Map {
|
||||
o, err := FromJSON(jsonString)
|
||||
|
||||
if err != nil {
|
||||
panic("objx: MustFromJSON failed with error: " + err.Error())
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
// FromJSON creates a new Map containing the data specified in the
|
||||
// jsonString.
|
||||
//
|
||||
// Returns an error if the JSON is invalid.
|
||||
func FromJSON(jsonString string) (Map, error) {
|
||||
|
||||
var data interface{}
|
||||
err := json.Unmarshal([]byte(jsonString), &data)
|
||||
|
||||
if err != nil {
|
||||
return Nil, err
|
||||
}
|
||||
|
||||
return New(data), nil
|
||||
|
||||
}
|
||||
|
||||
// FromBase64 creates a new Obj containing the data specified
|
||||
// in the Base64 string.
|
||||
//
|
||||
// The string is an encoded JSON string returned by Base64
|
||||
func FromBase64(base64String string) (Map, error) {
|
||||
|
||||
decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64String))
|
||||
|
||||
decoded, err := ioutil.ReadAll(decoder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return FromJSON(string(decoded))
|
||||
}
|
||||
|
||||
// MustFromBase64 creates a new Obj containing the data specified
|
||||
// in the Base64 string and panics if there is an error.
|
||||
//
|
||||
// The string is an encoded JSON string returned by Base64
|
||||
func MustFromBase64(base64String string) Map {
|
||||
|
||||
result, err := FromBase64(base64String)
|
||||
|
||||
if err != nil {
|
||||
panic("objx: MustFromBase64 failed with error: " + err.Error())
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// FromSignedBase64 creates a new Obj containing the data specified
|
||||
// in the Base64 string.
|
||||
//
|
||||
// The string is an encoded JSON string returned by SignedBase64
|
||||
func FromSignedBase64(base64String, key string) (Map, error) {
|
||||
parts := strings.Split(base64String, SignatureSeparator)
|
||||
if len(parts) != 2 {
|
||||
return nil, errors.New("objx: Signed base64 string is malformed.")
|
||||
}
|
||||
|
||||
sig := HashWithKey(parts[0], key)
|
||||
if parts[1] != sig {
|
||||
return nil, errors.New("objx: Signature for base64 data does not match.")
|
||||
}
|
||||
|
||||
return FromBase64(parts[0])
|
||||
}
|
||||
|
||||
// MustFromSignedBase64 creates a new Obj containing the data specified
|
||||
// in the Base64 string and panics if there is an error.
|
||||
//
|
||||
// The string is an encoded JSON string returned by Base64
|
||||
func MustFromSignedBase64(base64String, key string) Map {
|
||||
|
||||
result, err := FromSignedBase64(base64String, key)
|
||||
|
||||
if err != nil {
|
||||
panic("objx: MustFromSignedBase64 failed with error: " + err.Error())
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// FromURLQuery generates a new Obj by parsing the specified
|
||||
// query.
|
||||
//
|
||||
// For queries with multiple values, the first value is selected.
|
||||
func FromURLQuery(query string) (Map, error) {
|
||||
|
||||
vals, err := url.ParseQuery(query)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := make(map[string]interface{})
|
||||
for k, vals := range vals {
|
||||
m[k] = vals[0]
|
||||
}
|
||||
|
||||
return New(m), nil
|
||||
}
|
||||
|
||||
// MustFromURLQuery generates a new Obj by parsing the specified
|
||||
// query.
|
||||
//
|
||||
// For queries with multiple values, the first value is selected.
|
||||
//
|
||||
// Panics if it encounters an error
|
||||
func MustFromURLQuery(query string) Map {
|
||||
|
||||
o, err := FromURLQuery(query)
|
||||
|
||||
if err != nil {
|
||||
panic("objx: MustFromURLQuery failed with error: " + err.Error())
|
||||
}
|
||||
|
||||
return o
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package objx
|
||||
|
||||
var TestMap map[string]interface{} = map[string]interface{}{
|
||||
"name": "Tyler",
|
||||
"address": map[string]interface{}{
|
||||
"city": "Salt Lake City",
|
||||
"state": "UT",
|
||||
},
|
||||
"numbers": []interface{}{"one", "two", "three", "four", "five"},
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
package objx
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type Convertable struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (c *Convertable) MSI() map[string]interface{} {
|
||||
return map[string]interface{}{"name": c.name}
|
||||
}
|
||||
|
||||
type Unconvertable struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func TestMapCreation(t *testing.T) {
|
||||
|
||||
o := New(nil)
|
||||
assert.Nil(t, o)
|
||||
|
||||
o = New("Tyler")
|
||||
assert.Nil(t, o)
|
||||
|
||||
unconvertable := &Unconvertable{name: "Tyler"}
|
||||
o = New(unconvertable)
|
||||
assert.Nil(t, o)
|
||||
|
||||
convertable := &Convertable{name: "Tyler"}
|
||||
o = New(convertable)
|
||||
if assert.NotNil(t, convertable) {
|
||||
assert.Equal(t, "Tyler", o["name"], "Tyler")
|
||||
}
|
||||
|
||||
o = MSI()
|
||||
if assert.NotNil(t, o) {
|
||||
assert.NotNil(t, o)
|
||||
}
|
||||
|
||||
o = MSI("name", "Tyler")
|
||||
if assert.NotNil(t, o) {
|
||||
if assert.NotNil(t, o) {
|
||||
assert.Equal(t, o["name"], "Tyler")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMapMustFromJSONWithError(t *testing.T) {
|
||||
|
||||
_, err := FromJSON(`"name":"Mat"}`)
|
||||
assert.Error(t, err)
|
||||
|
||||
}
|
||||
|
||||
func TestMapFromJSON(t *testing.T) {
|
||||
|
||||
o := MustFromJSON(`{"name":"Mat"}`)
|
||||
|
||||
if assert.NotNil(t, o) {
|
||||
if assert.NotNil(t, o) {
|
||||
assert.Equal(t, "Mat", o["name"])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMapFromJSONWithError(t *testing.T) {
|
||||
|
||||
var m Map
|
||||
|
||||
assert.Panics(t, func() {
|
||||
m = MustFromJSON(`"name":"Mat"}`)
|
||||
})
|
||||
|
||||
assert.Nil(t, m)
|
||||
|
||||
}
|
||||
|
||||
func TestMapFromBase64String(t *testing.T) {
|
||||
|
||||
base64String := "eyJuYW1lIjoiTWF0In0="
|
||||
|
||||
o, err := FromBase64(base64String)
|
||||
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, o.Get("name").Str(), "Mat")
|
||||
}
|
||||
|
||||
assert.Equal(t, MustFromBase64(base64String).Get("name").Str(), "Mat")
|
||||
|
||||
}
|
||||
|
||||
func TestMapFromBase64StringWithError(t *testing.T) {
|
||||
|
||||
base64String := "eyJuYW1lIjoiTWFasd0In0="
|
||||
|
||||
_, err := FromBase64(base64String)
|
||||
|
||||
assert.Error(t, err)
|
||||
|
||||
assert.Panics(t, func() {
|
||||
MustFromBase64(base64String)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestMapFromSignedBase64String(t *testing.T) {
|
||||
|
||||
base64String := "eyJuYW1lIjoiTWF0In0=_67ee82916f90b2c0d68c903266e8998c9ef0c3d6"
|
||||
|
||||
o, err := FromSignedBase64(base64String, "key")
|
||||
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, o.Get("name").Str(), "Mat")
|
||||
}
|
||||
|
||||
assert.Equal(t, MustFromSignedBase64(base64String, "key").Get("name").Str(), "Mat")
|
||||
|
||||
}
|
||||
|
||||
func TestMapFromSignedBase64StringWithError(t *testing.T) {
|
||||
|
||||
base64String := "eyJuYW1lasdIjoiTWF0In0=_67ee82916f90b2c0d68c903266e8998c9ef0c3d6"
|
||||
|
||||
_, err := FromSignedBase64(base64String, "key")
|
||||
|
||||
assert.Error(t, err)
|
||||
|
||||
assert.Panics(t, func() {
|
||||
MustFromSignedBase64(base64String, "key")
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestMapFromURLQuery(t *testing.T) {
|
||||
|
||||
m, err := FromURLQuery("name=tyler&state=UT")
|
||||
if assert.NoError(t, err) && assert.NotNil(t, m) {
|
||||
assert.Equal(t, "tyler", m.Get("name").Str())
|
||||
assert.Equal(t, "UT", m.Get("state").Str())
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package objx
|
||||
|
||||
// Exclude returns a new Map with the keys in the specified []string
|
||||
// excluded.
|
||||
func (d Map) Exclude(exclude []string) Map {
|
||||
|
||||
excluded := make(Map)
|
||||
for k, v := range d {
|
||||
var shouldInclude bool = true
|
||||
for _, toExclude := range exclude {
|
||||
if k == toExclude {
|
||||
shouldInclude = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if shouldInclude {
|
||||
excluded[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return excluded
|
||||
}
|
||||
|
||||
// Copy creates a shallow copy of the Obj.
|
||||
func (m Map) Copy() Map {
|
||||
copied := make(map[string]interface{})
|
||||
for k, v := range m {
|
||||
copied[k] = v
|
||||
}
|
||||
return New(copied)
|
||||
}
|
||||
|
||||
// Merge blends the specified map with a copy of this map and returns the result.
|
||||
//
|
||||
// Keys that appear in both will be selected from the specified map.
|
||||
// This method requires that the wrapped object be a map[string]interface{}
|
||||
func (m Map) Merge(merge Map) Map {
|
||||
return m.Copy().MergeHere(merge)
|
||||
}
|
||||
|
||||
// Merge blends the specified map with this map and returns the current map.
|
||||
//
|
||||
// Keys that appear in both will be selected from the specified map. The original map
|
||||
// will be modified. This method requires that
|
||||
// the wrapped object be a map[string]interface{}
|
||||
func (m Map) MergeHere(merge Map) Map {
|
||||
|
||||
for k, v := range merge {
|
||||
m[k] = v
|
||||
}
|
||||
|
||||
return m
|
||||
|
||||
}
|
||||
|
||||
// Transform builds a new Obj giving the transformer a chance
|
||||
// to change the keys and values as it goes. This method requires that
|
||||
// the wrapped object be a map[string]interface{}
|
||||
func (m Map) Transform(transformer func(key string, value interface{}) (string, interface{})) Map {
|
||||
newMap := make(map[string]interface{})
|
||||
for k, v := range m {
|
||||
modifiedKey, modifiedVal := transformer(k, v)
|
||||
newMap[modifiedKey] = modifiedVal
|
||||
}
|
||||
return New(newMap)
|
||||
}
|
||||
|
||||
// TransformKeys builds a new map using the specified key mapping.
|
||||
//
|
||||
// Unspecified keys will be unaltered.
|
||||
// This method requires that the wrapped object be a map[string]interface{}
|
||||
func (m Map) TransformKeys(mapping map[string]string) Map {
|
||||
return m.Transform(func(key string, value interface{}) (string, interface{}) {
|
||||
|
||||
if newKey, ok := mapping[key]; ok {
|
||||
return newKey, value
|
||||
}
|
||||
|
||||
return key, value
|
||||
})
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package objx
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExclude(t *testing.T) {
|
||||
|
||||
d := make(Map)
|
||||
d["name"] = "Mat"
|
||||
d["age"] = 29
|
||||
d["secret"] = "ABC"
|
||||
|
||||
excluded := d.Exclude([]string{"secret"})
|
||||
|
||||
assert.Equal(t, d["name"], excluded["name"])
|
||||
assert.Equal(t, d["age"], excluded["age"])
|
||||
assert.False(t, excluded.Has("secret"), "secret should be excluded")
|
||||
|
||||
}
|
||||
|
||||
func TestCopy(t *testing.T) {
|
||||
|
||||
d1 := make(map[string]interface{})
|
||||
d1["name"] = "Tyler"
|
||||
d1["location"] = "UT"
|
||||
|
||||
d1Obj := New(d1)
|
||||
d2Obj := d1Obj.Copy()
|
||||
|
||||
d2Obj["name"] = "Mat"
|
||||
|
||||
assert.Equal(t, d1Obj.Get("name").Str(), "Tyler")
|
||||
assert.Equal(t, d2Obj.Get("name").Str(), "Mat")
|
||||
|
||||
}
|
||||
|
||||
func TestMerge(t *testing.T) {
|
||||
|
||||
d := make(map[string]interface{})
|
||||
d["name"] = "Mat"
|
||||
|
||||
d1 := make(map[string]interface{})
|
||||
d1["name"] = "Tyler"
|
||||
d1["location"] = "UT"
|
||||
|
||||
dObj := New(d)
|
||||
d1Obj := New(d1)
|
||||
|
||||
merged := dObj.Merge(d1Obj)
|
||||
|
||||
assert.Equal(t, merged.Get("name").Str(), d1Obj.Get("name").Str())
|
||||
assert.Equal(t, merged.Get("location").Str(), d1Obj.Get("location").Str())
|
||||
assert.Empty(t, dObj.Get("location").Str())
|
||||
|
||||
}
|
||||
|
||||
func TestMergeHere(t *testing.T) {
|
||||
|
||||
d := make(map[string]interface{})
|
||||
d["name"] = "Mat"
|
||||
|
||||
d1 := make(map[string]interface{})
|
||||
d1["name"] = "Tyler"
|
||||
d1["location"] = "UT"
|
||||
|
||||
dObj := New(d)
|
||||
d1Obj := New(d1)
|
||||
|
||||
merged := dObj.MergeHere(d1Obj)
|
||||
|
||||
assert.Equal(t, dObj, merged, "With MergeHere, it should return the first modified map")
|
||||
assert.Equal(t, merged.Get("name").Str(), d1Obj.Get("name").Str())
|
||||
assert.Equal(t, merged.Get("location").Str(), d1Obj.Get("location").Str())
|
||||
assert.Equal(t, merged.Get("location").Str(), dObj.Get("location").Str())
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package objx
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
// HashWithKey hashes the specified string using the security
|
||||
// key.
|
||||
func HashWithKey(data, key string) string {
|
||||
hash := sha1.New()
|
||||
hash.Write([]byte(data + ":" + key))
|
||||
return hex.EncodeToString(hash.Sum(nil))
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package objx
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHashWithKey(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "0ce84d8d01f2c7b6e0882b784429c54d280ea2d9", HashWithKey("abc", "def"))
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package objx
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSimpleExample(t *testing.T) {
|
||||
|
||||
// build a map from a JSON object
|
||||
o := MustFromJSON(`{"name":"Mat","foods":["indian","chinese"], "location":{"county":"hobbiton","city":"the shire"}}`)
|
||||
|
||||
// Map can be used as a straight map[string]interface{}
|
||||
assert.Equal(t, o["name"], "Mat")
|
||||
|
||||
// Get an Value object
|
||||
v := o.Get("name")
|
||||
assert.Equal(t, v, &Value{data: "Mat"})
|
||||
|
||||
// Test the contained value
|
||||
assert.False(t, v.IsInt())
|
||||
assert.False(t, v.IsBool())
|
||||
assert.True(t, v.IsStr())
|
||||
|
||||
// Get the contained value
|
||||
assert.Equal(t, v.Str(), "Mat")
|
||||
|
||||
// Get a default value if the contained value is not of the expected type or does not exist
|
||||
assert.Equal(t, 1, v.Int(1))
|
||||
|
||||
// Get a value by using array notation
|
||||
assert.Equal(t, "indian", o.Get("foods[0]").Data())
|
||||
|
||||
// Set a value by using array notation
|
||||
o.Set("foods[0]", "italian")
|
||||
assert.Equal(t, "italian", o.Get("foods[0]").Str())
|
||||
|
||||
// Get a value by using dot notation
|
||||
assert.Equal(t, "hobbiton", o.Get("location.county").Str())
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package objx
|
||||
|
||||
// Has gets whether there is something at the specified selector
|
||||
// or not.
|
||||
//
|
||||
// If m is nil, Has will always return false.
|
||||
func (m Map) Has(selector string) bool {
|
||||
if m == nil {
|
||||
return false
|
||||
}
|
||||
return !m.Get(selector).IsNil()
|
||||
}
|
||||
|
||||
// IsNil gets whether the data is nil or not.
|
||||
func (v *Value) IsNil() bool {
|
||||
return v == nil || v.data == nil
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package objx
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHas(t *testing.T) {
|
||||
|
||||
m := New(TestMap)
|
||||
|
||||
assert.True(t, m.Has("name"))
|
||||
assert.True(t, m.Has("address.state"))
|
||||
assert.True(t, m.Has("numbers[4]"))
|
||||
|
||||
assert.False(t, m.Has("address.state.nope"))
|
||||
assert.False(t, m.Has("address.nope"))
|
||||
assert.False(t, m.Has("nope"))
|
||||
assert.False(t, m.Has("numbers[5]"))
|
||||
|
||||
m = nil
|
||||
assert.False(t, m.Has("nothing"))
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,13 @@
|
|||
package objx
|
||||
|
||||
// Value provides methods for extracting interface{} data in various
|
||||
// types.
|
||||
type Value struct {
|
||||
// data contains the raw data being managed by this Value
|
||||
data interface{}
|
||||
}
|
||||
|
||||
// Data returns the raw data contained by this Value
|
||||
func (v *Value) Data() interface{} {
|
||||
return v.data
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package objx
|
Loading…
Reference in New Issue