<commit_before>package lightswarm

import (
	"bytes"
	"io/ioutil"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestFadeArgs(t *testing.T) {
	tt := []struct {
		name     string
		fade     Fade
		expected []byte
	}{
		{
			"fade to 255 at 1 step per 1 interval",
			Fade{255, 1, 1},
			[]byte{255, 1, 1},
		},
	}
	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			bs := tc.fade.Args()
			assert.Equal(t, tc.expected, bs)
		})
	}
}

func TestFrameAddress(t *testing.T) {
	tt := []struct {
		name    string
		address uint16
		b1      byte
		b2      byte
	}{
		{
			"lowest address (1)",
			1,
			0,
			1,
		},
		{
			"real address (690)",
			690,
			2,
			178,
		},
		{
			"maximum address (65535)",
			65535,
			255,
			255,
		},
	}
	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			f := Frame{Addr: tc.address}
			b1, b2 := f.address()
			assert.Equal(t, tc.b1, b1)
			assert.Equal(t, tc.b2, b2)
		})
	}
}

func TestFrameChecksum(t *testing.T) {
	tt := []struct {
		name     string
		bs       []byte
		expected byte
	}{
		{
			"simple bytes",
			[]byte{0, 1, 2, 3, 4},
			4,
		},
		{
			"real world bytes",
			[]byte{2, 178, ON},
			144,
		},
	}
	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			f := Frame{}
			checksum := f.checksum(tc.bs)
			assert.Equal(t, tc.expected, checksum)
		})
	}
}

func TestFrameWrap(t *testing.T) {
	tt := []struct {
		name     string
		bs       []byte
		expected []byte
	}{
		{
			"no bytes",
			[]byte{},
			[]byte{END, END},
		},
		{
			"end byte in bytes",
			[]byte{END},
			[]byte{END, ESC, 0xDC, END},
		},
		{
			"esc byte in bytes",
			[]byte{ESC},
			[]byte{END, ESC, 0xDD, END},
		},
		{
			"turn 690 on",
			[]byte{2, 178, ON, 144},
			[]byte{END, 2, 178, ON, 144, END},
		},
		{
			"turn 738 on",
			[]byte{2, 226, ON, 192},
			[]byte{END, 2, 226, ON, ESC, 0xDC, END},
		},
	}
	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			f := Frame{}
			frame := f.wrap(tc.bs)
			assert.Equal(t, tc.expected, frame)
		})
	}
}

func TestFrameBytes(t *testing.T) {
	tt := []struct {
		name     string
		frame    Frame
		expected []byte
	}{
		{
			"turn 690 on",
			Frame{690, ON, nil},
			[]byte{END, 2, 178, ON, 144, END},
		},
		{
			"fade 690 to 255 at 1 step per 1 interval",
			Frame{690, FADE_TO_LEVEL, []byte{255, 1, 1}},
			[]byte{END, 2, 178, FADE_TO_LEVEL, 255, 1, 1, 108, END},
		},
	}
	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			bs := tc.frame.Bytes()
			assert.Equal(t, tc.expected, bs)
		})
	}
}

func TestLEDOn(t *testing.T) {
	tt := []struct {
		name     string
		addr     uint16
		buff     *bytes.Buffer
		expected []byte
		n        int
		err      error
	}{
		{
			"turn 690 on",
			690,
			bytes.NewBuffer(nil),
			[]byte{END, 2, 178, ON, 144, END},
			6,
			nil,
		},
	}
	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			led := &LED{tc.addr, tc.buff}
			n, err := led.On()
			assert.Equal(t, tc.n, n)
			assert.Equal(t, tc.err, err)
			bs, err := ioutil.ReadAll(tc.buff)
			assert.Nil(t, err)
			assert.Equal(t, tc.expected, bs)
		})
	}
}

func TestLEDOff(t *testing.T) {
	tt := []struct {
		name     string
		addr     uint16
		buff     *bytes.Buffer
		expected []byte
		n        int
		err      error
	}{
		{
			"turn 690 off",
			690,
			bytes.NewBuffer(nil),
			[]byte{END, 2, 178, OFF, 145, END},
			6,
			nil,
		},
	}
	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			led := &LED{tc.addr, tc.buff}
			n, err := led.Off()
			assert.Equal(t, tc.n, n)
			assert.Equal(t, tc.err, err)
			bs, err := ioutil.ReadAll(tc.buff)
			assert.Nil(t, err)
			assert.Equal(t, tc.expected, bs)
		})
	}
}

func TestLEDFade(t *testing.T) {
	tt := []struct {
		name     string
		addr     uint16
		buff     *bytes.Buffer
		fade     Fade
		expected []byte
		n        int
		err      error
	}{
		{
			"fade 690 to 255 at 1 interval with 1 step",
			690,
			bytes.NewBuffer(nil),
			Fade{255, 1, 1},
			[]byte{END, 2, 178, FADE_TO_LEVEL, 255, 1, 1, 108, END},
			9,
			nil,
		},
	}
	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			led := &LED{tc.addr, tc.buff}
			n, err := led.Fade(tc.fade)
			assert.Equal(t, tc.n, n)
			assert.Equal(t, tc.err, err)
			bs, err := ioutil.ReadAll(tc.buff)
			assert.Nil(t, err)
			assert.Equal(t, tc.expected, bs)
		})
	}
}

func TestLEDSetRGB(t *testing.T) {
	tt := []struct {
		name     string
		addr     uint16
		buff     *bytes.Buffer
		red      byte
		green    byte
		blue     byte
		expected []byte
		n        int
		err      error
	}{
		{
			"set 690 RGB to 85, 199, 237",
			690,
			bytes.NewBuffer(nil),
			85, 199, 237, // RGB
			[]byte{END, 2, 178, SET_RGB_LEVELS, 85, 199, 237, 227, END},
			9,
			nil,
		},
	}
	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			led := &LED{tc.addr, tc.buff}
			n, err := led.SetRGB(tc.red, tc.green, tc.blue)
			assert.Equal(t, tc.n, n)
			assert.Equal(t, tc.err, err)
			bs, err := ioutil.ReadAll(tc.buff)
			assert.Nil(t, err)
			assert.Equal(t, tc.expected, bs)
		})
	}
}
<commit_msg>Test for LED RGB fade<commit_after>package lightswarm

import (
	"bytes"
	"io"
	"io/ioutil"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestFadeArgs(t *testing.T) {
	tt := []struct {
		name     string
		fade     Fade
		expected []byte
	}{
		{
			"fade to 255 at 1 step per 1 interval",
			Fade{255, 1, 1},
			[]byte{255, 1, 1},
		},
	}
	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			bs := tc.fade.Args()
			assert.Equal(t, tc.expected, bs)
		})
	}
}

func TestFrameAddress(t *testing.T) {
	tt := []struct {
		name    string
		address uint16
		b1      byte
		b2      byte
	}{
		{
			"lowest address (1)",
			1,
			0,
			1,
		},
		{
			"real address (690)",
			690,
			2,
			178,
		},
		{
			"maximum address (65535)",
			65535,
			255,
			255,
		},
	}
	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			f := Frame{Addr: tc.address}
			b1, b2 := f.address()
			assert.Equal(t, tc.b1, b1)
			assert.Equal(t, tc.b2, b2)
		})
	}
}

func TestFrameChecksum(t *testing.T) {
	tt := []struct {
		name     string
		bs       []byte
		expected byte
	}{
		{
			"simple bytes",
			[]byte{0, 1, 2, 3, 4},
			4,
		},
		{
			"real world bytes",
			[]byte{2, 178, ON},
			144,
		},
	}
	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			f := Frame{}
			checksum := f.checksum(tc.bs)
			assert.Equal(t, tc.expected, checksum)
		})
	}
}

func TestFrameWrap(t *testing.T) {
	tt := []struct {
		name     string
		bs       []byte
		expected []byte
	}{
		{
			"no bytes",
			[]byte{},
			[]byte{END, END},
		},
		{
			"end byte in bytes",
			[]byte{END},
			[]byte{END, ESC, 0xDC, END},
		},
		{
			"esc byte in bytes",
			[]byte{ESC},
			[]byte{END, ESC, 0xDD, END},
		},
		{
			"turn 690 on",
			[]byte{2, 178, ON, 144},
			[]byte{END, 2, 178, ON, 144, END},
		},
		{
			"turn 738 on",
			[]byte{2, 226, ON, 192},
			[]byte{END, 2, 226, ON, ESC, 0xDC, END},
		},
	}
	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			f := Frame{}
			frame := f.wrap(tc.bs)
			assert.Equal(t, tc.expected, frame)
		})
	}
}

func TestFrameBytes(t *testing.T) {
	tt := []struct {
		name     string
		frame    Frame
		expected []byte
	}{
		{
			"turn 690 on",
			Frame{690, ON, nil},
			[]byte{END, 2, 178, ON, 144, END},
		},
		{
			"fade 690 to 255 at 1 step per 1 interval",
			Frame{690, FADE_TO_LEVEL, []byte{255, 1, 1}},
			[]byte{END, 2, 178, FADE_TO_LEVEL, 255, 1, 1, 108, END},
		},
	}
	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			bs := tc.frame.Bytes()
			assert.Equal(t, tc.expected, bs)
		})
	}
}

func TestLEDOn(t *testing.T) {
	tt := []struct {
		name     string
		addr     uint16
		buff     *bytes.Buffer
		expected []byte
		n        int
		err      error
	}{
		{
			"turn 690 on",
			690,
			bytes.NewBuffer(nil),
			[]byte{END, 2, 178, ON, 144, END},
			6,
			nil,
		},
	}
	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			led := &LED{tc.addr, tc.buff}
			n, err := led.On()
			assert.Equal(t, tc.n, n)
			assert.Equal(t, tc.err, err)
			bs, err := ioutil.ReadAll(tc.buff)
			assert.Nil(t, err)
			assert.Equal(t, tc.expected, bs)
		})
	}
}

func TestLEDOff(t *testing.T) {
	tt := []struct {
		name     string
		addr     uint16
		buff     *bytes.Buffer
		expected []byte
		n        int
		err      error
	}{
		{
			"turn 690 off",
			690,
			bytes.NewBuffer(nil),
			[]byte{END, 2, 178, OFF, 145, END},
			6,
			nil,
		},
	}
	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			led := &LED{tc.addr, tc.buff}
			n, err := led.Off()
			assert.Equal(t, tc.n, n)
			assert.Equal(t, tc.err, err)
			bs, err := ioutil.ReadAll(tc.buff)
			assert.Nil(t, err)
			assert.Equal(t, tc.expected, bs)
		})
	}
}

func TestLEDFade(t *testing.T) {
	tt := []struct {
		name     string
		addr     uint16
		buff     *bytes.Buffer
		fade     Fade
		expected []byte
		n        int
		err      error
	}{
		{
			"fade 690 to 255 at 1 interval with 1 step",
			690,
			bytes.NewBuffer(nil),
			Fade{255, 1, 1},
			[]byte{END, 2, 178, FADE_TO_LEVEL, 255, 1, 1, 108, END},
			9,
			nil,
		},
	}
	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			led := &LED{tc.addr, tc.buff}
			n, err := led.Fade(tc.fade)
			assert.Equal(t, tc.n, n)
			assert.Equal(t, tc.err, err)
			bs, err := ioutil.ReadAll(tc.buff)
			assert.Nil(t, err)
			assert.Equal(t, tc.expected, bs)
		})
	}
}

func TestLEDSetRGB(t *testing.T) {
	tt := []struct {
		name     string
		addr     uint16
		buff     *bytes.Buffer
		red      byte
		green    byte
		blue     byte
		expected []byte
		n        int
		err      error
	}{
		{
			"set 690 RGB to 85, 199, 237",
			690,
			bytes.NewBuffer(nil),
			85, 199, 237, // RGB
			[]byte{END, 2, 178, SET_RGB_LEVELS, 85, 199, 237, 227, END},
			9,
			nil,
		},
	}
	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			led := &LED{tc.addr, tc.buff}
			n, err := led.SetRGB(tc.red, tc.green, tc.blue)
			assert.Equal(t, tc.n, n)
			assert.Equal(t, tc.err, err)
			bs, err := ioutil.ReadAll(tc.buff)
			assert.Nil(t, err)
			assert.Equal(t, tc.expected, bs)
		})
	}
}

func TestTestFadeRGB(t *testing.T) {
	tt := []struct {
		name     string
		addr     uint16
		buff     *bytes.Buffer
		red      Fade
		green    Fade
		blue     Fade
		expected []byte
		n        int
		err      error
	}{
		{
			"set fade 690 RGB to 85, 199, 237",
			690,
			bytes.NewBuffer(nil),
			Fade{85, 1, 1}, Fade{199, 1, 1}, Fade{237, 1, 1}, // RGB
			[]byte{END, 2, 178, FADE_RGB_TO_LEVEL, 85, 1, 1, 199, 1, 1, 237, 1, 1, 254, END},
			15,
			nil,
		},
	}
	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			led := &LED{tc.addr, tc.buff}
			n, err := led.FadeRGB(tc.red, tc.green, tc.blue)
			assert.Equal(t, tc.n, n)
			assert.Equal(t, tc.err, err)
			bs, err := ioutil.ReadAll(tc.buff)
			assert.Nil(t, err)
			assert.Equal(t, tc.expected, bs)
		})
	}
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"

	"github.com/hyperledger/fabric/core/chaincode/shim"
)

type SimpleChaincode struct {
}

func main() {
	err := shim.Start(new(SimpleChaincode))

	if err != nil {
		//do nothing
	}
}

func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
	// added by chenlin@20170308
	if len(args) != 0 {
		return nil, errors.New("incorrect args")
	}
	// adminCert, err := stub.GetCallerMetadata()
	// if err != nil {
	// 	return nil, errors.New("failed getting metadata")
	// }
	// if len(adminCert) == 0 {
	// 	return nil, errors.New("invalid admin certificate. Empty")
	// }
	stub.PutState("admin", []byte("hahahahaha"))
	err := stub.CreateTable("AssetsOwnership", []*shim.ColumnDefinition{
		&shim.ColumnDefinition{Name: "Asset", Type: shim.ColumnDefinition_STRING, Key: true},
		&shim.ColumnDefinition{Name: "Owner", Type: shim.ColumnDefinition_BYTES, Key: false},
	})
	if err != nil {
		return nil, errors.New("Failed creating AssetsOnwership table.")
	}
	return nil, nil
}

func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
	switch function {
	case "putState":
		if len(args) != 2 {
			return nil, errors.New("incorrect args")
		}
		key := args[0]
		value := []byte(args[1])
		err := stub.PutState(key, value)
		if err != nil {
			return nil, err
		}
		return value, err
	case "delState":
		if len(args) != 1 {
			return nil, errors.New("incorrect args")
		}
		key := args[0]
		err := stub.DelState(key)
		if err != nil {
			return nil, err
		}
		result := key + "has been deleted"
		return []byte(result), err

	case "createTable":
		err := stub.CreateTable("AssetsOwnership", []*shim.ColumnDefinition{
			&shim.ColumnDefinition{Name: "Asset", Type: shim.ColumnDefinition_STRING, Key: true},
			&shim.ColumnDefinition{Name: "Owner", Type: shim.ColumnDefinition_BYTES, Key: false},
		})
		if err != nil {
			return nil, errors.New("Failed creating AssetsOnwership table.")
		}
		return nil, nil

	case "insertTable":
		if len(args) != 2 {
			return nil, errors.New("incorrect args")
		}
		asset := args[0]
		owner := []byte(args[1])
		row := shim.Row{
			Columns: []*shim.Column{
				&shim.Column{Value: &shim.Column_String_{String_: asset}},
				&shim.Column{Value: &shim.Column_Bytes{Bytes: owner}}}}
		ok, err := stub.InsertRow("AssetsOwnership", row)
		if !ok && err == nil {
			return nil, errors.New("asset was already assigned")
		}
		return nil, nil //end of insertTable

	}
	return nil, nil
}

func (t *SimpleChaincode) Query(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
	switch function {
	case "getTxID":
		txID := stub.GetTxID()
		result := []byte(txID)
		return result, nil
	case "getTxTimestamp":
		time, err := stub.GetTxTimestamp()
		result := []byte(time.String()) //时间转换为字符串，time.String()
		return result, err
	case "getStringArgs":
		strList := stub.GetStringArgs()
		var result string
		for index := 0; index < len(strList); index++ {
			result += "***" + strList[index] + "***"
		}
		return []byte(result), nil

	case "getState":
		if len(args) != 1 {
			return nil, errors.New("incorrect args")
		}
		key := args[0]
		result, err := stub.GetState(key)

		if err != nil {
			return nil, errors.New("Failed in function getState")
		}
		return result, err

	case "getCallerCert":
		if len(args) != 0 {
			return nil, errors.New("incorrect args")
		}
		result, err := stub.GetCallerCertificate()
		if err != nil {
			return nil, errors.New("Failed in function getCallerCert")
		}
		return result, err

	case "getCallerMetadata":
		if len(args) != 0 {
			return nil, errors.New("incorrect args")
		}
		result, err := stub.GetCallerMetadata()
		if err != nil {
			return nil, errors.New("Failed in function getCallerMetadata")
		}
		fmt.Println(result)
		return result, err
		// return []byte("getCallerMetadata"), err
	case "getBinding":
		if len(args) != 0 {
			return nil, errors.New("incorrect args")
		}
		result, err := stub.GetBinding()

		if err != nil {
			return nil, errors.New("Failed in function getBinding")
		}
		fmt.Println(result)
		return result, err
		// return []byte("getBinding"), err
	case "getPayload":
		if len(args) != 0 {
			return nil, errors.New("incorrect args")
		}
		result, err := stub.GetPayload()

		if err != nil {
			return nil, errors.New("Failed in function getPayload")
		}
		fmt.Println(result)
		return result, err
	// return []byte("getPayload"), err
	case "getRow":
		fmt.Println("*********************")
		fmt.Println("*********************")
		fmt.Println("*********************")
		if len(args) != 1 {
			return nil, errors.New("incorrect args")
		}
		asset := args[0]
		var columns []shim.Column
		col1 := shim.Column{Value: &shim.Column_String_{String_: asset}}
		columns = append(columns, col1)

		row, err := stub.GetRow("AssetsOwnership", columns)
		if err != nil {
			return nil, errors.New("failed in function getRow")
		}

		val0 := row.Columns[0].GetString_()
		val1 := row.Columns[1].GetString_()
		result := "****" + val0 + "****" + val1
		return []byte(result), err
	//end of "getRow"

	case "getRowDefinition":
		if len(args) != 2 {
			return nil, errors.New("incorrect args")
		}
		asset := args[0]
		owner := args[1]

		col1 := shim.Column{Value: &shim.Column_String_{String_: asset}}
		col2 := shim.Column{Value: &shim.Column_String_{String_: owner}}
		// var columns []shim.Column
		// columns = append(columns, col1)
		// columns = append(columns, col2)

		// val0 := columns[0].GetString_()
		// val1 := columns[1].GetString_()
		// result := asset + "********" + string(owner) + "*****end of input ****"
		// result += col1.GetString_() + "****" + col2.GetString_()
		result := col1.GetString_() + "****" + col2.GetString_()
		return []byte(result), nil
	}

	return nil, nil
}
<commit_msg>finished function: getRowDefinition<commit_after>package main

import (
	"errors"
	"fmt"

	"github.com/hyperledger/fabric/core/chaincode/shim"
)

type SimpleChaincode struct {
}

func main() {
	err := shim.Start(new(SimpleChaincode))

	if err != nil {
		//do nothing
	}
}

func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
	// added by chenlin@20170308
	if len(args) != 0 {
		return nil, errors.New("incorrect args")
	}
	// adminCert, err := stub.GetCallerMetadata()
	// if err != nil {
	// 	return nil, errors.New("failed getting metadata")
	// }
	// if len(adminCert) == 0 {
	// 	return nil, errors.New("invalid admin certificate. Empty")
	// }
	stub.PutState("admin", []byte("hahahahaha"))
	err := stub.CreateTable("AssetsOwnership", []*shim.ColumnDefinition{
		&shim.ColumnDefinition{Name: "Asset", Type: shim.ColumnDefinition_STRING, Key: true},
		&shim.ColumnDefinition{Name: "Owner", Type: shim.ColumnDefinition_BYTES, Key: false},
	})
	if err != nil {
		return nil, errors.New("Failed creating AssetsOnwership table.")
	}
	return nil, nil
}

func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
	switch function {
	case "putState":
		if len(args) != 2 {
			return nil, errors.New("incorrect args")
		}
		key := args[0]
		value := []byte(args[1])
		err := stub.PutState(key, value)
		if err != nil {
			return nil, err
		}
		return value, err
	case "delState":
		if len(args) != 1 {
			return nil, errors.New("incorrect args")
		}
		key := args[0]
		err := stub.DelState(key)
		if err != nil {
			return nil, err
		}
		result := key + "has been deleted"
		return []byte(result), err

	case "createTable":
		err := stub.CreateTable("AssetsOwnership", []*shim.ColumnDefinition{
			&shim.ColumnDefinition{Name: "Asset", Type: shim.ColumnDefinition_STRING, Key: true},
			&shim.ColumnDefinition{Name: "Owner", Type: shim.ColumnDefinition_BYTES, Key: false},
		})
		if err != nil {
			return nil, errors.New("Failed creating AssetsOnwership table.")
		}
		return nil, nil

	case "insertTable":
		if len(args) != 2 {
			return nil, errors.New("incorrect args")
		}
		asset := args[0]
		owner := []byte(args[1])
		row := shim.Row{
			Columns: []*shim.Column{
				&shim.Column{Value: &shim.Column_String_{String_: asset}},
				&shim.Column{Value: &shim.Column_Bytes{Bytes: owner}}}}
		ok, err := stub.InsertRow("AssetsOwnership", row)
		if !ok && err == nil {
			return nil, errors.New("asset was already assigned")
		}
		return nil, nil //end of insertTable

	}
	return nil, nil
}

func (t *SimpleChaincode) Query(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
	switch function {
	case "getTxID":
		txID := stub.GetTxID()
		result := []byte(txID)
		return result, nil
	case "getTxTimestamp":
		time, err := stub.GetTxTimestamp()
		result := []byte(time.String()) //时间转换为字符串，time.String()
		return result, err
	case "getStringArgs":
		strList := stub.GetStringArgs()
		var result string
		for index := 0; index < len(strList); index++ {
			result += "***" + strList[index] + "***"
		}
		return []byte(result), nil

	case "getState":
		if len(args) != 1 {
			return nil, errors.New("incorrect args")
		}
		key := args[0]
		result, err := stub.GetState(key)

		if err != nil {
			return nil, errors.New("Failed in function getState")
		}
		return result, err

	case "getCallerCert":
		if len(args) != 0 {
			return nil, errors.New("incorrect args")
		}
		result, err := stub.GetCallerCertificate()
		if err != nil {
			return nil, errors.New("Failed in function getCallerCert")
		}
		return result, err

	case "getCallerMetadata":
		if len(args) != 0 {
			return nil, errors.New("incorrect args")
		}
		result, err := stub.GetCallerMetadata()
		if err != nil {
			return nil, errors.New("Failed in function getCallerMetadata")
		}
		fmt.Println(result)
		return result, err
		// return []byte("getCallerMetadata"), err
	case "getBinding":
		if len(args) != 0 {
			return nil, errors.New("incorrect args")
		}
		result, err := stub.GetBinding()

		if err != nil {
			return nil, errors.New("Failed in function getBinding")
		}
		fmt.Println(result)
		return result, err
		// return []byte("getBinding"), err
	case "getPayload":
		if len(args) != 0 {
			return nil, errors.New("incorrect args")
		}
		result, err := stub.GetPayload()

		if err != nil {
			return nil, errors.New("Failed in function getPayload")
		}
		fmt.Println(result)
		return result, err
	// return []byte("getPayload"), err
	case "getRow":
		fmt.Println("*********************")
		fmt.Println("*********************")
		fmt.Println("*********************")
		if len(args) != 1 {
			return nil, errors.New("incorrect args")
		}
		asset := args[0]
		var columns []shim.Column
		col1 := shim.Column{Value: &shim.Column_String_{String_: asset}}
		columns = append(columns, col1)

		row, err := stub.GetRow("AssetsOwnership", columns)
		if err != nil {
			return nil, errors.New("failed in function getRow")
		}

		val0 := row.Columns[0].GetString_()
		val1 := row.Columns[1].GetString_()
		result := "****" + val0 + "****" + val1
		return []byte(result), err
	//end of "getRow"

	case "getRowDefinition":
		if len(args) != 2 {
			return nil, errors.New("incorrect args")
		}
		asset := args[0]
		owner := args[1]

		col0 := shim.Column{Value: &shim.Column_String_{String_: asset}} //col0，type=shim.column
		col1 := shim.Column{Value: &shim.Column_String_{String_: owner}} //col1, type=shim.column
		var columns []shim.Column                                        //define columns=[]shim.column
		columns = append(columns, col0)
		columns = append(columns, col1) //完成columns的赋值

		val0 := columns[0].GetString_()
		val1 := columns[1].GetString_()
		result := val0 + "****" + val1
		return []byte(result), nil
	}

	return nil, nil
}
<|endoftext|>


<commit_before>/*
   Nging is a toolbox for webmasters
   Copyright (C) 2018-present  Wenhui Shen <swh@admpub.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package backend

import (
	"net/http"
	"strconv"
	"strings"

	"github.com/webx-top/com"
	"github.com/webx-top/echo"
	"github.com/webx-top/echo/handler/pprof"
	"github.com/webx-top/echo/middleware"
	"github.com/webx-top/echo/middleware/language"
	"github.com/webx-top/echo/middleware/render"
	"github.com/webx-top/echo/middleware/render/driver"
	"github.com/webx-top/echo/middleware/session"
	"github.com/webx-top/echo/subdomains"

	"github.com/arl/statsviz"

	"github.com/admpub/log"
	"github.com/admpub/nging/v3/application/cmd/event"
	"github.com/admpub/nging/v3/application/handler"
	"github.com/admpub/nging/v3/application/library/common"
	"github.com/admpub/nging/v3/application/library/config"
	"github.com/admpub/nging/v3/application/library/formbuilder"
	ngingMW "github.com/admpub/nging/v3/application/middleware"
)

const (
	DefaultTemplateDir   = `./template/backend`
	DefaultAssetsDir     = `./public/assets`
	DefaultAssetsURLPath = `/public/assets/backend`
)

var (
	TemplateDir      = DefaultTemplateDir //模板文件夹
	AssetsDir        = DefaultAssetsDir   //素材文件夹
	AssetsURLPath    = DefaultAssetsURLPath
	DefaultAvatarURL = AssetsURLPath + `/images/user_128.png`
	RendererDo       = func(driver.Driver) {}
	ParseStrings     = map[string]string{}
	ParseStringFuncs = map[string]func() string{}
	SkippedGzipPaths = map[string]bool{}
	GzipSkipper      = func(skippedPaths map[string]bool) func(c echo.Context) bool {
		return func(c echo.Context) bool {
			skipped, _ := skippedPaths[c.Request().URL().Path()]
			if !skipped {
				skipped, _ = skippedPaths[c.Path()]
			}
			return skipped
		}
	}
	DefaultLocalHostNames = []string{
		`127.0.0.1`, `localhost`,
	}
)

func MakeSubdomains(domain string, appends []string) []string {
	domainList := strings.Split(domain, `,`)
	domain = domainList[0]
	if pos := strings.Index(domain, `://`); pos > 0 {
		pos += 3
		if pos < len(domain) {
			domain = domain[pos:]
		} else {
			domain = ``
		}
	}
	var myPort string
	domain, myPort = com.SplitHost(domain)
	if len(myPort) == 0 && len(domainList) > 1 {
		_, myPort = com.SplitHost(domainList[1])
	}
	port := strconv.Itoa(config.DefaultCLIConfig.Port)
	newDomainList := []string{}
	if !com.InSlice(domain+`:`+port, domainList) {
		newDomainList = append(newDomainList, domain+`:`+port)
	}
	if myPort == port {
		myPort = ``
	}
	if len(myPort) > 0 {
		if !com.InSlice(domain+`:`+myPort, domainList) {
			newDomainList = append(newDomainList, domain+`:`+myPort)
		}
	}
	for _, hostName := range appends {
		if hostName == domain {
			continue
		}
		if !com.InSlice(hostName+`:`+port, domainList) {
			newDomainList = append(newDomainList, hostName+`:`+port)
		}
		if len(myPort) > 0 {
			if !com.InSlice(hostName+`:`+myPort, domainList) {
				newDomainList = append(newDomainList, hostName+`:`+myPort)
			}
		}
	}
	if len(newDomainList) > 0 {
		domainList = append(domainList, newDomainList...)
	}
	return domainList
}

func init() {
	echo.Set(`BackendPrefix`, handler.BackendPrefix)
	echo.Set(`GlobalPrefix`, handler.GlobalPrefix)
	event.OnStart(0, func() {
		handler.GlobalPrefix = echo.String(`GlobalPrefix`)
		handler.BackendPrefix = echo.String(`BackendPrefix`)
		handler.FrontendPrefix = echo.String(`FrontendPrefix`)
		ngingMW.DefaultAvatarURL = DefaultAssetsURLPath
		e := handler.Echo()
		e.SetPrefix(handler.GlobalPrefix)
		handler.SetRootGroup(handler.BackendPrefix)
		subdomains.Default.Default = `backend`
		subdomains.Default.Boot = `backend`
		domainName := subdomains.Default.Default
		backendDomain := config.DefaultCLIConfig.BackendDomain
		if len(backendDomain) > 0 {
			domainName += `@` + strings.Join(MakeSubdomains(backendDomain, DefaultLocalHostNames), `,`)
		}
		subdomains.Default.Add(domainName, e)

		e.Use(middleware.Log(), middleware.Recover())
		e.Use(middleware.Gzip(&middleware.GzipConfig{
			Skipper: GzipSkipper(SkippedGzipPaths),
		}))
		e.Use(func(h echo.Handler) echo.HandlerFunc {
			return func(c echo.Context) error {
				c.Response().Header().Set(`Server`, event.SoftwareName+`/`+config.Version.Number)
				return h.Handle(c)
			}
		})

		// 注册静态资源文件(网站素材文件)
		e.Use(event.StaticMW) //打包的静态资源
		// 上传文件资源(改到manager中用File函数实现)
		// e.Use(middleware.Static(&middleware.StaticOptions{
		// 	Root: helper.UploadDir,
		// 	Path: helper.UploadURLPath,
		// }))

		// 启用session
		e.Use(session.Middleware(config.SessionOptions))
		// 启用多语言支持
		config.DefaultConfig.Language.SetFSFunc(event.LangFSFunc)
		i18n := language.New(&config.DefaultConfig.Language)
		e.Use(i18n.Middleware())

		// 启用Validation
		e.Use(middleware.Validate(echo.NewValidation))

		// 事物支持
		e.Use(ngingMW.Transaction())
		// 注册模板引擎
		renderOptions := &render.Config{
			TmplDir: TemplateDir,
			Engine:  `standard`,
			ParseStrings: map[string]string{
				`__TMPL__`: TemplateDir,
			},
			DefaultHTTPErrorCode: http.StatusOK,
			Reload:               true,
			ErrorPages:           config.DefaultConfig.Sys.ErrorPages,
			ErrorProcessors:      common.ErrorProcessors,
		}
		for key, val := range ParseStrings {
			renderOptions.ParseStrings[key] = val
		}
		for key, val := range ParseStringFuncs {
			renderOptions.ParseStringFuncs[key] = val
		}
		if RendererDo != nil {
			renderOptions.AddRendererDo(RendererDo)
		}
		renderOptions.AddFuncSetter(BackendURLFunc)
		renderOptions.AddFuncSetter(ngingMW.ErrorPageFunc)
		renderOptions.ApplyTo(e, event.BackendTmplMgr)
		renderOptions.Renderer().MonitorEvent(func(file string) {
			if strings.HasSuffix(file, `.form.json`) {
				if formbuilder.DelCachedConfig(file) {
					log.Debug(`delete: cache form config: `, file)
				}
			}
		})
		//RendererDo(renderOptions.Renderer())
		echo.On(`clearCache`, func(_ echo.H) error {
			log.Debug(`clear: Backend Template Object Cache`)
			renderOptions.Renderer().ClearCache()
			formbuilder.ClearCache()
			return nil
		})
		e.Get(`/favicon.ico`, event.FaviconHandler)
		i18n.Handler(e, `App.i18n`)
		if event.Develop {
			pprof.Wrap(e)
			mux := http.NewServeMux()
			_ = statsviz.Register(mux)
			// Use echo WrapHandler to wrap statsviz ServeMux as echo HandleFunc
			e.Get("/debug/statsviz/", echo.WrapHandler(mux))
			// Serve static content for statsviz UI
			e.Get("/debug/statsviz/*", echo.WrapHandler(mux))
		}
		Initialize()
	})
}
<commit_msg>update<commit_after>/*
   Nging is a toolbox for webmasters
   Copyright (C) 2018-present  Wenhui Shen <swh@admpub.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package backend

import (
	"net/http"
	"strconv"
	"strings"

	"github.com/webx-top/com"
	"github.com/webx-top/echo"
	"github.com/webx-top/echo/handler/pprof"
	"github.com/webx-top/echo/middleware"
	"github.com/webx-top/echo/middleware/language"
	"github.com/webx-top/echo/middleware/render"
	"github.com/webx-top/echo/middleware/render/driver"
	"github.com/webx-top/echo/middleware/session"
	"github.com/webx-top/echo/param"
	"github.com/webx-top/echo/subdomains"

	"github.com/arl/statsviz"

	"github.com/admpub/log"
	"github.com/admpub/nging/v3/application/cmd/event"
	"github.com/admpub/nging/v3/application/handler"
	"github.com/admpub/nging/v3/application/library/common"
	"github.com/admpub/nging/v3/application/library/config"
	"github.com/admpub/nging/v3/application/library/formbuilder"
	ngingMW "github.com/admpub/nging/v3/application/middleware"
)

const (
	DefaultTemplateDir   = `./template/backend`
	DefaultAssetsDir     = `./public/assets`
	DefaultAssetsURLPath = `/public/assets/backend`
)

var (
	TemplateDir      = DefaultTemplateDir //模板文件夹
	AssetsDir        = DefaultAssetsDir   //素材文件夹
	AssetsURLPath    = DefaultAssetsURLPath
	DefaultAvatarURL = AssetsURLPath + `/images/user_128.png`
	RendererDo       = func(driver.Driver) {}
	ParseStrings     = map[string]string{}
	ParseStringFuncs = map[string]func() string{}
	SkippedGzipPaths = map[string]bool{}
	GzipSkipper      = func(skippedPaths map[string]bool) func(c echo.Context) bool {
		return func(c echo.Context) bool {
			skipped, _ := skippedPaths[c.Request().URL().Path()]
			if !skipped {
				skipped, _ = skippedPaths[c.Path()]
			}
			return skipped
		}
	}
	DefaultLocalHostNames = []string{
		`127.0.0.1`, `localhost`,
	}
)

func MakeSubdomains(domain string, appends []string) []string {
	domainList := strings.Split(domain, `,`)
	domain = domainList[0]
	if pos := strings.Index(domain, `://`); pos > 0 {
		pos += 3
		if pos < len(domain) {
			domain = domain[pos:]
		} else {
			domain = ``
		}
	}
	var myPort string
	domain, myPort = com.SplitHost(domain)
	if len(myPort) == 0 && len(domainList) > 1 {
		_, myPort = com.SplitHost(domainList[1])
	}
	port := strconv.Itoa(config.DefaultCLIConfig.Port)
	newDomainList := []string{}
	if !com.InSlice(domain+`:`+port, domainList) {
		newDomainList = append(newDomainList, domain+`:`+port)
	}
	if myPort == port {
		myPort = ``
	}
	if len(myPort) > 0 {
		if !com.InSlice(domain+`:`+myPort, domainList) {
			newDomainList = append(newDomainList, domain+`:`+myPort)
		}
	}
	for _, hostName := range appends {
		if hostName == domain {
			continue
		}
		newDomainList = append(newDomainList, hostName+`:`+port)
		if len(myPort) > 0 {
			newDomainList = append(newDomainList, hostName+`:`+myPort)
		}
	}
	if len(newDomainList) > 0 {
		domainList = append(domainList, newDomainList...)
	}
	return param.StringSlice(domainList).Unique().String()
}

func init() {
	echo.Set(`BackendPrefix`, handler.BackendPrefix)
	echo.Set(`GlobalPrefix`, handler.GlobalPrefix)
	event.OnStart(0, func() {
		handler.GlobalPrefix = echo.String(`GlobalPrefix`)
		handler.BackendPrefix = echo.String(`BackendPrefix`)
		handler.FrontendPrefix = echo.String(`FrontendPrefix`)
		ngingMW.DefaultAvatarURL = DefaultAssetsURLPath
		e := handler.Echo()
		e.SetPrefix(handler.GlobalPrefix)
		handler.SetRootGroup(handler.BackendPrefix)
		subdomains.Default.Default = `backend`
		subdomains.Default.Boot = `backend`
		domainName := subdomains.Default.Default
		backendDomain := config.DefaultCLIConfig.BackendDomain
		if len(backendDomain) > 0 {
			domainName += `@` + strings.Join(MakeSubdomains(backendDomain, DefaultLocalHostNames), `,`)
		}
		subdomains.Default.Add(domainName, e)

		e.Use(middleware.Log(), middleware.Recover())
		e.Use(middleware.Gzip(&middleware.GzipConfig{
			Skipper: GzipSkipper(SkippedGzipPaths),
		}))
		e.Use(func(h echo.Handler) echo.HandlerFunc {
			return func(c echo.Context) error {
				c.Response().Header().Set(`Server`, event.SoftwareName+`/`+config.Version.Number)
				return h.Handle(c)
			}
		})

		// 注册静态资源文件(网站素材文件)
		e.Use(event.StaticMW) //打包的静态资源
		// 上传文件资源(改到manager中用File函数实现)
		// e.Use(middleware.Static(&middleware.StaticOptions{
		// 	Root: helper.UploadDir,
		// 	Path: helper.UploadURLPath,
		// }))

		// 启用session
		e.Use(session.Middleware(config.SessionOptions))
		// 启用多语言支持
		config.DefaultConfig.Language.SetFSFunc(event.LangFSFunc)
		i18n := language.New(&config.DefaultConfig.Language)
		e.Use(i18n.Middleware())

		// 启用Validation
		e.Use(middleware.Validate(echo.NewValidation))

		// 事物支持
		e.Use(ngingMW.Transaction())
		// 注册模板引擎
		renderOptions := &render.Config{
			TmplDir: TemplateDir,
			Engine:  `standard`,
			ParseStrings: map[string]string{
				`__TMPL__`: TemplateDir,
			},
			DefaultHTTPErrorCode: http.StatusOK,
			Reload:               true,
			ErrorPages:           config.DefaultConfig.Sys.ErrorPages,
			ErrorProcessors:      common.ErrorProcessors,
		}
		for key, val := range ParseStrings {
			renderOptions.ParseStrings[key] = val
		}
		for key, val := range ParseStringFuncs {
			renderOptions.ParseStringFuncs[key] = val
		}
		if RendererDo != nil {
			renderOptions.AddRendererDo(RendererDo)
		}
		renderOptions.AddFuncSetter(BackendURLFunc)
		renderOptions.AddFuncSetter(ngingMW.ErrorPageFunc)
		renderOptions.ApplyTo(e, event.BackendTmplMgr)
		renderOptions.Renderer().MonitorEvent(func(file string) {
			if strings.HasSuffix(file, `.form.json`) {
				if formbuilder.DelCachedConfig(file) {
					log.Debug(`delete: cache form config: `, file)
				}
			}
		})
		//RendererDo(renderOptions.Renderer())
		echo.On(`clearCache`, func(_ echo.H) error {
			log.Debug(`clear: Backend Template Object Cache`)
			renderOptions.Renderer().ClearCache()
			formbuilder.ClearCache()
			return nil
		})
		e.Get(`/favicon.ico`, event.FaviconHandler)
		i18n.Handler(e, `App.i18n`)
		if event.Develop {
			pprof.Wrap(e)
			mux := http.NewServeMux()
			_ = statsviz.Register(mux)
			// Use echo WrapHandler to wrap statsviz ServeMux as echo HandleFunc
			e.Get("/debug/statsviz/", echo.WrapHandler(mux))
			// Serve static content for statsviz UI
			e.Get("/debug/statsviz/*", echo.WrapHandler(mux))
		}
		Initialize()
	})
}
<|endoftext|>


<commit_before>// SPDX-License-Identifier: MIT

package assert

import (
	"fmt"
	"os"
	"path"
	"reflect"
	"runtime"
	"strconv"
	"strings"
	"testing"
)

// 定位错误信息的触发函数。输出格式为：TestXxx(xxx_test.go:17)。
func getCallerInfo() string {
	var info string

	for i := 0; ; i++ {
		pc, file, line, ok := runtime.Caller(i)
		if !ok {
			break
		}

		// 定位以 _test.go 结尾的文件。
		basename := path.Base(file)
		if !strings.HasSuffix(basename, "_test.go") {
			continue
		}

		// 定位函数名为 Test 开头的行。
		// 为什么要定位到 TestXxx 函数，是因为考虑以下情况：
		//  func isOK(val interface{}, t *testing.T) {
		//      // do somthing
		//      assert.True(t, val)  // (1
		//  }
		//
		//  func TestOK(t *testing.T) {
		//      isOK("123", t)       // (2
		//      isOK(123, t)         // (3
		//  }
		// 以上这段代码，定位到 (2、(3 的位置比总是定位到 (1 的位置更直观！
		funcName := runtime.FuncForPC(pc).Name()
		index := strings.LastIndex(funcName, ".Test")
		if -1 == index {
			index = strings.LastIndex(funcName, ".Benchmark")
			if index == -1 {
				continue
			}
		}
		funcName = funcName[index+1:]

		// Go1.5 之后的匿名函数为 TestA.func1
		// 包含以下几种情况：
		// 调用函数内的匿名函数；
		// 采用 go func(){} 的形式调用函数内的匿名函数；
		// 采用 go func(){} 的形式调用外部函数；
		//
		// 但是无法处理 go xx() 的情况，该情况直接开启一个新的堆栈信息，无法定位当前函数中的调用位置。
		if index := strings.IndexByte(funcName, '.'); index > -1 {
			funcName = funcName[:index]
			info = funcName + "(" + basename + ":" + strconv.Itoa(line) + ")"
			continue
		}

		info = funcName + "(" + basename + ":" + strconv.Itoa(line) + ")"
		break
	}

	if info == "" {
		info = "<无法获取调用者信息>"
	}
	return info
}

// 格式化错误提示信息。
//
// msg1 中的所有参数将依次被传递给 fmt.Sprintf() 函数，
// 所以 msg1[0] 必须可以转换成 string(如:string, []byte, []rune, fmt.Stringer)
//
// msg2 参数格式与 msg1 完全相同，在 msg1 为空的情况下，会使用 msg2 的内容，
// 否则 msg2 不会启作用。
func formatMessage(msg1 []interface{}, msg2 []interface{}) string {
	msg := msg1
	if len(msg) == 0 {
		msg = msg2
	}

	if len(msg) == 0 {
		return "<未提供任何错误信息>"
	}

	if len(msg) == 1 {
		return fmt.Sprint(msg[0])
	}

	format := ""
	switch v := msg[0].(type) {
	case []byte:
		format = string(v)
	case []rune:
		format = string(v)
	case string:
		format = v
	case fmt.Stringer:
		format = v.String()
	default:
		return fmt.Sprintln(msg...)
	}

	return fmt.Sprintf(format, msg[1:]...)
}

// 当 expr 条件不成立时，输出错误信息。
//
// expr 返回结果值为bool类型的表达式；
// msg1,msg2 输出的错误信息，之所以提供两组信息，是方便在用户没有提供的情况下，
// 可以使用系统内部提供的信息，优先使用 msg1 中的信息，若不存在，则使用 msg2 的内容。
func assert(t testing.TB, expr bool, msg1 []interface{}, msg2 []interface{}) {
	if !expr {
		t.Error(formatMessage(msg1, msg2) + "@" + getCallerInfo())
	}
}

// True 断言表达式 expr 为 true，否则输出错误信息。
//
// args 对应 fmt.Printf() 函数中的参数，其中 args[0] 对应第一个参数 format，依次类推，
// 具体可参数 formatMessage() 函数的介绍。其它断言函数的 args 参数，功能与此相同。
func True(t testing.TB, expr bool, args ...interface{}) {
	assert(t, expr, args, []interface{}{"True 失败，实际值为[%T:%[1]v]", expr})
}

// False 断言表达式 expr 为 false，否则输出错误信息
func False(t testing.TB, expr bool, args ...interface{}) {
	assert(t, !expr, args, []interface{}{"False 失败，实际值为[%T:%[1]v]", expr})
}

// Nil 断言表达式 expr 为 nil，否则输出错误信息
func Nil(t testing.TB, expr interface{}, args ...interface{}) {
	assert(t, IsNil(expr), args, []interface{}{"Nil 失败，实际值为[%T:%[1]v]", expr})
}

// NotNil 断言表达式 expr 为非 nil 值，否则输出错误信息
func NotNil(t testing.TB, expr interface{}, args ...interface{}) {
	assert(t, !IsNil(expr), args, []interface{}{"NotNil 失败，实际值为[%T:%[1]v]", expr})
}

// Equal 断言 v1 与 v2 两个值相等，否则输出错误信息
func Equal(t testing.TB, v1, v2 interface{}, args ...interface{}) {
	assert(t, IsEqual(v1, v2), args, []interface{}{"Equal 失败，实际值为\nv1=[%T:%[1]v]\nv2=[%T:%[2]v]", v1, v2})
}

// NotEqual 断言 v1 与 v2 两个值不相等，否则输出错误信息
func NotEqual(t testing.TB, v1, v2 interface{}, args ...interface{}) {
	assert(t, !IsEqual(v1, v2), args, []interface{}{"NotEqual 失败，实际值为\nv1=[%T:%[1]v]\nv2=[%T:%[2]v]", v1, v2})
}

// Empty 断言 expr 的值为空(nil,"",0,false)，否则输出错误信息
func Empty(t testing.TB, expr interface{}, args ...interface{}) {
	assert(t, IsEmpty(expr), args, []interface{}{"Empty 失败，实际值为[%T:%[1]v]", expr})
}

// NotEmpty 断言 expr 的值为非空(除 nil,"",0,false之外)，否则输出错误信息
func NotEmpty(t testing.TB, expr interface{}, args ...interface{}) {
	assert(t, !IsEmpty(expr), args, []interface{}{"NotEmpty 失败，实际值为[%T:%[1]v]", expr})
}

// Error 断言有错误发生，否则输出错误信息。
// 传递未初始化的 error 值(var err error = nil)，将断言失败
func Error(t testing.TB, expr interface{}, args ...interface{}) {
	if IsNil(expr) { // 空值，必定没有错误
		assert(t, false, args, []interface{}{"Error 失败，实际值为 Nil：[%T]", expr})
		return
	}

	_, ok := expr.(error)
	assert(t, ok, args, []interface{}{"Error 失败，实际类型为[%T]", expr})
}

// ErrorString 断言有错误发生，且错误信息中包含指定的字符串 str。
// 传递未初始化的 error 值(var err error = nil)，将断言失败
func ErrorString(t testing.TB, expr interface{}, str string, args ...interface{}) {
	if IsNil(expr) { // 空值，必定没有错误
		assert(t, false, args, []interface{}{"ErrorString 失败，实际值为 Nil：[%T]", expr})
		return
	}

	if err, ok := expr.(error); ok {
		index := strings.Index(err.Error(), str)
		assert(t, index >= 0, args, []interface{}{"Error 失败，实际类型为[%T]", expr})
	}
}

// ErrorType 断言有错误发生，且错误的类型与 typ 的类型相同。
// 传递未初始化的 error 值(var err error = nil)，将断言失败
func ErrorType(t testing.TB, expr interface{}, typ error, args ...interface{}) {
	if IsNil(expr) { // 空值，必定没有错误
		assert(t, false, args, []interface{}{"ErrorType 失败，实际值为 Nil：[%T]", expr})
		return
	}

	if _, ok := expr.(error); !ok {
		assert(t, false, args, []interface{}{"ErrorType 失败，实际类型为[%T]，且无法转换成 error 接口", expr})
		return
	}

	t1 := reflect.TypeOf(expr)
	t2 := reflect.TypeOf(typ)
	assert(t, t1 == t2, args, []interface{}{"ErrorType 失败，v1[%v]为一个错误类型，但与v2[%v]的类型不相同", t1, t2})
}

// NotError 断言没有错误发生，否则输出错误信息
func NotError(t testing.TB, expr interface{}, args ...interface{}) {
	if IsNil(expr) { // 空值必定没有错误
		assert(t, true, args, []interface{}{"NotError 失败，实际类型为[%T]", expr})
		return
	}
	err, ok := expr.(error)
	assert(t, !ok, args, []interface{}{"NotError 失败，错误信息为[%v]", err})
}

// FileExists 断言文件存在，否则输出错误信息
func FileExists(t testing.TB, path string, args ...interface{}) {
	_, err := os.Stat(path)

	if err != nil && !os.IsExist(err) {
		assert(t, false, args, []interface{}{"FileExists 失败，且附带以下错误：%v", err})
	}
}

// FileNotExists 断言文件不存在，否则输出错误信息
func FileNotExists(t testing.TB, path string, args ...interface{}) {
	_, err := os.Stat(path)

	if err == nil {
		assert(t, false, args, []interface{}{"FileNotExists 失败"})
	}
	if os.IsExist(err) {
		assert(t, false, args, []interface{}{"FileNotExists 失败，且返回以下错误信息：%v", err})
	}
}

// Panic 断言函数会发生 panic，否则输出错误信息。
func Panic(t testing.TB, fn func(), args ...interface{}) {
	has, _ := HasPanic(fn)
	assert(t, has, args, []interface{}{"并未发生 panic"})
}

// PanicString 断言函数会发生 panic，且 panic 信息中包含指定的字符串内容，否则输出错误信息。
func PanicString(t testing.TB, fn func(), str string, args ...interface{}) {
	if has, msg := HasPanic(fn); has {
		index := strings.Index(fmt.Sprint(msg), str)
		assert(t, index >= 0, args, []interface{}{"并未发生 panic"})
	}
}

// PanicType 断言函数会发生 panic，且 panic 返回的类型与 typ 的类型相同。
func PanicType(t testing.TB, fn func(), typ interface{}, args ...interface{}) {
	has, msg := HasPanic(fn)
	if !has {
		return
	}

	t1 := reflect.TypeOf(msg)
	t2 := reflect.TypeOf(typ)
	assert(t, t1 == t2, args, []interface{}{"PanicType 失败，v1[%v]的类型与v2[%v]的类型不相同", t1, t2})

}

// NotPanic 断言函数会发生 panic，否则输出错误信息。
func NotPanic(t testing.TB, fn func(), args ...interface{}) {
	has, msg := HasPanic(fn)
	assert(t, !has, args, []interface{}{"发生了 panic，其信息为[%v]", msg})
}

// Contains 断言 container 包含 item 的或是包含 item 中的所有项
// 具体函数说明可参考 IsContains()
func Contains(t testing.TB, container, item interface{}, args ...interface{}) {
	assert(t, IsContains(container, item), args,
		[]interface{}{"container:[%v]并未包含item[%v]", container, item})
}

// NotContains 断言 container 不包含 item 的或是不包含 item 中的所有项
func NotContains(t testing.TB, container, item interface{}, args ...interface{}) {
	assert(t, !IsContains(container, item), args,
		[]interface{}{"container:[%v]包含item[%v]", container, item})
}
<commit_msg>refactor: 格式化输出信息<commit_after>// SPDX-License-Identifier: MIT

package assert

import (
	"fmt"
	"os"
	"path"
	"reflect"
	"runtime"
	"strconv"
	"strings"
	"testing"
)

// 定位错误信息的触发函数。输出格式为：TestXxx(xxx_test.go:17)。
func getCallerInfo() string {
	var info string

	for i := 0; ; i++ {
		pc, file, line, ok := runtime.Caller(i)
		if !ok {
			break
		}

		// 定位以 _test.go 结尾的文件。
		basename := path.Base(file)
		if !strings.HasSuffix(basename, "_test.go") {
			continue
		}

		// 定位函数名为 Test 开头的行。
		// 为什么要定位到 TestXxx 函数，是因为考虑以下情况：
		//  func isOK(val interface{}, t *testing.T) {
		//      // do somthing
		//      assert.True(t, val)  // (1
		//  }
		//
		//  func TestOK(t *testing.T) {
		//      isOK("123", t)       // (2
		//      isOK(123, t)         // (3
		//  }
		// 以上这段代码，定位到 (2、(3 的位置比总是定位到 (1 的位置更直观！
		funcName := runtime.FuncForPC(pc).Name()
		index := strings.LastIndex(funcName, ".Test")
		if -1 == index {
			index = strings.LastIndex(funcName, ".Benchmark")
			if index == -1 {
				continue
			}
		}
		funcName = funcName[index+1:]

		// Go1.5 之后的匿名函数为 TestA.func1
		// 包含以下几种情况：
		// 调用函数内的匿名函数；
		// 采用 go func(){} 的形式调用函数内的匿名函数；
		// 采用 go func(){} 的形式调用外部函数；
		//
		// 但是无法处理 go xx() 的情况，该情况直接开启一个新的堆栈信息，无法定位当前函数中的调用位置。
		if index := strings.IndexByte(funcName, '.'); index > -1 {
			funcName = funcName[:index]
			info = funcName + "(" + basename + ":" + strconv.Itoa(line) + ")"
			continue
		}

		info = funcName + "(" + basename + ":" + strconv.Itoa(line) + ")"
		break
	}

	if info == "" {
		info = "<无法获取调用者信息>"
	}
	return info
}

// 格式化错误提示信息。
//
// msg1 中的所有参数将依次被传递给 fmt.Sprintf() 函数，
// 所以 msg1[0] 必须可以转换成 string(如:string, []byte, []rune, fmt.Stringer)
//
// msg2 参数格式与 msg1 完全相同，在 msg1 为空的情况下，会使用 msg2 的内容，
// 否则 msg2 不会启作用。
func formatMessage(msg1 []interface{}, msg2 []interface{}) string {
	msg := msg1
	if len(msg) == 0 {
		msg = msg2
	}

	if len(msg) == 0 {
		return "<未提供任何错误信息>"
	}

	if len(msg) == 1 {
		return fmt.Sprint(msg[0])
	}

	format := ""
	switch v := msg[0].(type) {
	case []byte:
		format = string(v)
	case []rune:
		format = string(v)
	case string:
		format = v
	case fmt.Stringer:
		format = v.String()
	default:
		return fmt.Sprintln(msg...)
	}

	return fmt.Sprintf(format, msg[1:]...)
}

// 当 expr 条件不成立时，输出错误信息。
//
// expr 返回结果值为bool类型的表达式；
// msg1,msg2 输出的错误信息，之所以提供两组信息，是方便在用户没有提供的情况下，
// 可以使用系统内部提供的信息，优先使用 msg1 中的信息，若不存在，则使用 msg2 的内容。
func assert(t testing.TB, expr bool, msg1 []interface{}, msg2 []interface{}) {
	if !expr {
		t.Error(formatMessage(msg1, msg2) + "@" + getCallerInfo())
	}
}

// True 断言表达式 expr 为 true，否则输出错误信息。
//
// args 对应 fmt.Printf() 函数中的参数，其中 args[0] 对应第一个参数 format，依次类推，
// 具体可参数 formatMessage() 函数的介绍。其它断言函数的 args 参数，功能与此相同。
func True(t testing.TB, expr bool, args ...interface{}) {
	assert(t, expr, args, []interface{}{"True 失败，实际值为 %#v", expr})
}

// False 断言表达式 expr 为 false，否则输出错误信息
func False(t testing.TB, expr bool, args ...interface{}) {
	assert(t, !expr, args, []interface{}{"False 失败，实际值为 %#v", expr})
}

// Nil 断言表达式 expr 为 nil，否则输出错误信息
func Nil(t testing.TB, expr interface{}, args ...interface{}) {
	assert(t, IsNil(expr), args, []interface{}{"Nil 失败，实际值为 %#v", expr})
}

// NotNil 断言表达式 expr 为非 nil 值，否则输出错误信息
func NotNil(t testing.TB, expr interface{}, args ...interface{}) {
	assert(t, !IsNil(expr), args, []interface{}{"NotNil 失败，实际值为 %#v", expr})
}

// Equal 断言 v1 与 v2 两个值相等，否则输出错误信息
func Equal(t testing.TB, v1, v2 interface{}, args ...interface{}) {
	assert(t, IsEqual(v1, v2), args, []interface{}{"Equal 失败，实际值为\nv1=%#v\nv2=%#v", v1, v2})
}

// NotEqual 断言 v1 与 v2 两个值不相等，否则输出错误信息
func NotEqual(t testing.TB, v1, v2 interface{}, args ...interface{}) {
	assert(t, !IsEqual(v1, v2), args, []interface{}{"NotEqual 失败，实际值为\nv1=%#v\nv2=%#v", v1, v2})
}

// Empty 断言 expr 的值为空(nil,"",0,false)，否则输出错误信息
func Empty(t testing.TB, expr interface{}, args ...interface{}) {
	assert(t, IsEmpty(expr), args, []interface{}{"Empty 失败，实际值为 %#v", expr})
}

// NotEmpty 断言 expr 的值为非空(除 nil,"",0,false之外)，否则输出错误信息
func NotEmpty(t testing.TB, expr interface{}, args ...interface{}) {
	assert(t, !IsEmpty(expr), args, []interface{}{"NotEmpty 失败，实际值为 %#v", expr})
}

// Error 断言有错误发生，否则输出错误信息。
// 传递未初始化的 error 值(var err error = nil)，将断言失败
func Error(t testing.TB, expr interface{}, args ...interface{}) {
	if IsNil(expr) { // 空值，必定没有错误
		assert(t, false, args, []interface{}{"Error 失败，实际值为 Nil：[%T]", expr})
		return
	}

	_, ok := expr.(error)
	assert(t, ok, args, []interface{}{"Error 失败，实际类型为[%T]", expr})
}

// ErrorString 断言有错误发生，且错误信息中包含指定的字符串 str。
// 传递未初始化的 error 值(var err error = nil)，将断言失败
func ErrorString(t testing.TB, expr interface{}, str string, args ...interface{}) {
	if IsNil(expr) { // 空值，必定没有错误
		assert(t, false, args, []interface{}{"ErrorString 失败，实际值为 Nil：[%T]", expr})
		return
	}

	if err, ok := expr.(error); ok {
		index := strings.Index(err.Error(), str)
		assert(t, index >= 0, args, []interface{}{"Error 失败，实际类型为[%T]", expr})
	}
}

// ErrorType 断言有错误发生，且错误的类型与 typ 的类型相同。
// 传递未初始化的 error 值(var err error = nil)，将断言失败
func ErrorType(t testing.TB, expr interface{}, typ error, args ...interface{}) {
	if IsNil(expr) { // 空值，必定没有错误
		assert(t, false, args, []interface{}{"ErrorType 失败，实际值为 Nil：[%T]", expr})
		return
	}

	if _, ok := expr.(error); !ok {
		assert(t, false, args, []interface{}{"ErrorType 失败，实际类型为[%T]，且无法转换成 error 接口", expr})
		return
	}

	t1 := reflect.TypeOf(expr)
	t2 := reflect.TypeOf(typ)
	assert(t, t1 == t2, args, []interface{}{"ErrorType 失败，v1[%v]为一个错误类型，但与v2[%v]的类型不相同", t1, t2})
}

// NotError 断言没有错误发生，否则输出错误信息
func NotError(t testing.TB, expr interface{}, args ...interface{}) {
	if IsNil(expr) { // 空值必定没有错误
		assert(t, true, args, []interface{}{"NotError 失败，实际类型为[%T]", expr})
		return
	}
	err, ok := expr.(error)
	assert(t, !ok, args, []interface{}{"NotError 失败，错误信息为[%v]", err})
}

// FileExists 断言文件存在，否则输出错误信息
func FileExists(t testing.TB, path string, args ...interface{}) {
	_, err := os.Stat(path)

	if err != nil && !os.IsExist(err) {
		assert(t, false, args, []interface{}{"FileExists 失败，且附带以下错误：%v", err})
	}
}

// FileNotExists 断言文件不存在，否则输出错误信息
func FileNotExists(t testing.TB, path string, args ...interface{}) {
	_, err := os.Stat(path)

	if err == nil {
		assert(t, false, args, []interface{}{"FileNotExists 失败"})
	}
	if os.IsExist(err) {
		assert(t, false, args, []interface{}{"FileNotExists 失败，且返回以下错误信息：%v", err})
	}
}

// Panic 断言函数会发生 panic，否则输出错误信息。
func Panic(t testing.TB, fn func(), args ...interface{}) {
	has, _ := HasPanic(fn)
	assert(t, has, args, []interface{}{"并未发生 panic"})
}

// PanicString 断言函数会发生 panic，且 panic 信息中包含指定的字符串内容，否则输出错误信息。
func PanicString(t testing.TB, fn func(), str string, args ...interface{}) {
	if has, msg := HasPanic(fn); has {
		index := strings.Index(fmt.Sprint(msg), str)
		assert(t, index >= 0, args, []interface{}{"并未发生 panic"})
	}
}

// PanicType 断言函数会发生 panic，且 panic 返回的类型与 typ 的类型相同。
func PanicType(t testing.TB, fn func(), typ interface{}, args ...interface{}) {
	has, msg := HasPanic(fn)
	if !has {
		return
	}

	t1 := reflect.TypeOf(msg)
	t2 := reflect.TypeOf(typ)
	assert(t, t1 == t2, args, []interface{}{"PanicType 失败，v1[%v]的类型与v2[%v]的类型不相同", t1, t2})

}

// NotPanic 断言函数会发生 panic，否则输出错误信息。
func NotPanic(t testing.TB, fn func(), args ...interface{}) {
	has, msg := HasPanic(fn)
	assert(t, !has, args, []interface{}{"发生了 panic，其信息为[%v]", msg})
}

// Contains 断言 container 包含 item 的或是包含 item 中的所有项
// 具体函数说明可参考 IsContains()
func Contains(t testing.TB, container, item interface{}, args ...interface{}) {
	assert(t, IsContains(container, item), args,
		[]interface{}{"container:[%v]并未包含item[%v]", container, item})
}

// NotContains 断言 container 不包含 item 的或是不包含 item 中的所有项
func NotContains(t testing.TB, container, item interface{}, args ...interface{}) {
	assert(t, !IsContains(container, item), args,
		[]interface{}{"container:[%v]包含item[%v]", container, item})
}
<|endoftext|>


<commit_before>package httpclient

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"reflect"
	"strings"

	"github.com/gobs/pretty"
	"github.com/gobs/simplejson"
)

type HttpResponse struct {
	http.Response
}

func (r *HttpResponse) ContentType() string {
	content_type := r.Header.Get("Content-Type")
	if len(content_type) == 0 {
		return content_type
	}

	return strings.TrimSpace(strings.Split(content_type, ";")[0])
}

//
// Check if the input value is a "primitive" that can be safely stringified
//
func canStringify(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.Bool,
		reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Float32, reflect.Float64,
		reflect.String:
		return true
	default:
		return false
	}
}

//
// Given a base URL and a bag of parameteters returns the URL with the encoded parameters
//
func URLWithPathParams(base string, path string, params map[string]interface{}) (u *url.URL) {

	u, err := url.Parse(base)
	if err != nil {
		log.Fatal(err)
	}

	if len(path) > 0 {
		u, err = u.Parse(path)
		if err != nil {
			log.Fatal(err)
		}
	}

	q := u.Query()

	for k, v := range params {
		val := reflect.ValueOf(v)

		switch val.Kind() {
		case reflect.Slice:
			if val.IsNil() { // TODO: add an option to ignore empty values
				q.Set(k, "")
				continue
			}
			fallthrough

		case reflect.Array:
			for i := 0; i < val.Len(); i++ {
				av := val.Index(i)

				if canStringify(av) {
					q.Add(k, fmt.Sprintf("%v", av))
				}
			}

		default:
			if canStringify(val) {
				q.Set(k, fmt.Sprintf("%v", v))
			} else {
				log.Fatal("Invalid type ", val)
			}
		}
	}

	u.RawQuery = q.Encode()
	return u
}

func URLWithParams(base string, params map[string]interface{}) (u *url.URL) {
	return URLWithPathParams(base, "", params)
}

//
// http.Get with params
//
func Get(urlStr string, params map[string]interface{}) (*HttpResponse, error) {
	resp, err := http.Get(URLWithParams(urlStr, params).String())
	if err == nil {
		return &HttpResponse{*resp}, nil
	} else {
		return nil, err
	}
}

//
// http.Post with params
//
func Post(urlStr string, params map[string]interface{}) (*HttpResponse, error) {
	resp, err := http.PostForm(urlStr, URLWithParams(urlStr, params).Query())
	if err == nil {
		return &HttpResponse{*resp}, nil
	} else {
		return nil, err
	}
}

//
//  Read the body
//
func (resp *HttpResponse) Content() []byte {
	body, _ := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	return body
}

//
//  Try to parse the response body as JSON
//
func (resp *HttpResponse) Json() (json *simplejson.Json) {
	json, _ = simplejson.LoadBytes(resp.Content())
	return
}

////////////////////////////////////////////////////////////////////////

//
// http.Client with some defaults and stuff
//
type HttpClient struct {
	// the http.Client
	client *http.Client

	// the base URL for this client
	BaseURL *url.URL

	// the client UserAgent string
	UserAgent string

	// Common headers to be passed on each request
	Headers map[string]string

	// if Verbose, log request and response info
	Verbose bool
}

//
// Create a new HttpClient
//
func NewHttpClient(base string) (httpClient *HttpClient) {
	httpClient = new(HttpClient)
	httpClient.client = &http.Client{CheckRedirect: httpClient.checkRedirect}
	httpClient.Headers = make(map[string]string)

	if u, err := url.Parse(base); err != nil {
		log.Fatal(err)
	} else {
		httpClient.BaseURL = u
	}

	return
}

//
// add default headers plus extra headers
//
func (self *HttpClient) addHeaders(req *http.Request, headers map[string]string) {

	if len(self.UserAgent) > 0 {
		req.Header.Set("User-Agent", self.UserAgent)
	}

	for k, v := range self.Headers {
		req.Header.Set(k, v)
	}

	for k, v := range headers {
		req.Header.Set(k, v)
	}
}

//
// the callback for CheckRedirect, used to pass along the headers in case of redirection
//
func (self *HttpClient) checkRedirect(req *http.Request, via []*http.Request) error {
	if self.Verbose {
		log.Println("REDIRECT:", len(via), req.URL)
	}

	if len(via) >= 10 {
		return errors.New("stopped after 10 redirects")
	}

	// TODO: check for same host before adding headers
	self.addHeaders(req, nil)
	return nil
}

//
// Create a request object given the method, path, body and extra headers
//
func (self *HttpClient) Request(method string, urlpath string, body io.Reader, headers map[string]string) (req *http.Request) {
	if u, err := self.BaseURL.Parse(urlpath); err != nil {
		log.Fatal(err)
	} else {
		urlpath = u.String()
	}

	req, err := http.NewRequest(method, urlpath, body)
	if err != nil {
		log.Fatal(err)
	}

	self.addHeaders(req, headers)
	return
}

//
// Execute request
//
func (self *HttpClient) Do(req *http.Request) (*HttpResponse, error) {
	if self.Verbose {
		log.Println("REQUEST:", req.Method, req.URL, pretty.PrettyFormat(req.Header))
	}

	resp, err := self.client.Do(req)
	if err == nil {
		if self.Verbose {
			log.Println("RESPONSE:", resp.Status, pretty.PrettyFormat(resp.Header))
		}

		return &HttpResponse{*resp}, nil
	} else {
		if self.Verbose {
			log.Println("ERROR:", err)
		}

		return nil, err
	}
}

//
// Execute a DELETE request
//
func (self *HttpClient) Delete(path string, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("DELETE", path, nil, headers)
	return self.Do(req)
}

//
// Execute a HEAD request
//
func (self *HttpClient) Head(path string, params map[string]interface{}, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("HEAD", URLWithParams(path, params).String(), nil, headers)
	return self.Do(req)
}

//
// Execute a GET request
//
func (self *HttpClient) Get(path string, params map[string]interface{}, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("GET", URLWithParams(path, params).String(), nil, headers)
	return self.Do(req)
}

//
// Execute a POST request
//
func (self *HttpClient) Post(path string, content io.Reader, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("POST", path, content, headers)
	if headers != nil {
		for k, v := range headers {
			req.Header.Set(k, v)
		}
	}
	return self.Do(req)
}
<commit_msg>Added "AllowInsecure" method to accept insecure HTTPS connections (i.e. to servers with self-signed or expired certificates). You shouldn't use it, but when you need it it's there.<commit_after>package httpclient

import (
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"reflect"
	"strings"

	"github.com/gobs/pretty"
	"github.com/gobs/simplejson"
)

var (
	DefaultClient = &http.Client{} // we use our own default client, so we can change the TLS configuration
)

//
// Allow connections via HTTPS even if something is wrong with the certificate
// (self-signed or expired)
//
func AllowInsecure(insecure bool) {
	if insecure {
		tr := &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}

		DefaultClient.Transport = tr
	} else {
		DefaultClient.Transport = nil
	}
}

type HttpResponse struct {
	http.Response
}

func (r *HttpResponse) ContentType() string {
	content_type := r.Header.Get("Content-Type")
	if len(content_type) == 0 {
		return content_type
	}

	return strings.TrimSpace(strings.Split(content_type, ";")[0])
}

//
// Check if the input value is a "primitive" that can be safely stringified
//
func canStringify(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.Bool,
		reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Float32, reflect.Float64,
		reflect.String:
		return true
	default:
		return false
	}
}

//
// Given a base URL and a bag of parameteters returns the URL with the encoded parameters
//
func URLWithPathParams(base string, path string, params map[string]interface{}) (u *url.URL) {

	u, err := url.Parse(base)
	if err != nil {
		log.Fatal(err)
	}

	if len(path) > 0 {
		u, err = u.Parse(path)
		if err != nil {
			log.Fatal(err)
		}
	}

	q := u.Query()

	for k, v := range params {
		val := reflect.ValueOf(v)

		switch val.Kind() {
		case reflect.Slice:
			if val.IsNil() { // TODO: add an option to ignore empty values
				q.Set(k, "")
				continue
			}
			fallthrough

		case reflect.Array:
			for i := 0; i < val.Len(); i++ {
				av := val.Index(i)

				if canStringify(av) {
					q.Add(k, fmt.Sprintf("%v", av))
				}
			}

		default:
			if canStringify(val) {
				q.Set(k, fmt.Sprintf("%v", v))
			} else {
				log.Fatal("Invalid type ", val)
			}
		}
	}

	u.RawQuery = q.Encode()
	return u
}

func URLWithParams(base string, params map[string]interface{}) (u *url.URL) {
	return URLWithPathParams(base, "", params)
}

//
// http.Get with params
//
func Get(urlStr string, params map[string]interface{}) (*HttpResponse, error) {
	resp, err := DefaultClient.Get(URLWithParams(urlStr, params).String())
	if err == nil {
		return &HttpResponse{*resp}, nil
	} else {
		return nil, err
	}
}

//
// http.Post with params
//
func Post(urlStr string, params map[string]interface{}) (*HttpResponse, error) {
	resp, err := DefaultClient.PostForm(urlStr, URLWithParams(urlStr, params).Query())
	if err == nil {
		return &HttpResponse{*resp}, nil
	} else {
		return nil, err
	}
}

//
//  Read the body
//
func (resp *HttpResponse) Content() []byte {
	body, _ := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	return body
}

//
//  Try to parse the response body as JSON
//
func (resp *HttpResponse) Json() (json *simplejson.Json) {
	json, _ = simplejson.LoadBytes(resp.Content())
	return
}

////////////////////////////////////////////////////////////////////////

//
// http.Client with some defaults and stuff
//
type HttpClient struct {
	// the http.Client
	client *http.Client

	// the base URL for this client
	BaseURL *url.URL

	// the client UserAgent string
	UserAgent string

	// Common headers to be passed on each request
	Headers map[string]string

	// if Verbose, log request and response info
	Verbose bool
}

//
// Create a new HttpClient
//
func NewHttpClient(base string) (httpClient *HttpClient) {
	httpClient = new(HttpClient)
	httpClient.client = &http.Client{CheckRedirect: httpClient.checkRedirect}
	httpClient.Headers = make(map[string]string)

	if u, err := url.Parse(base); err != nil {
		log.Fatal(err)
	} else {
		httpClient.BaseURL = u
	}

	return
}

//
// Allow connections via HTTPS even if something is wrong with the certificate
// (self-signed or expired)
//
func (self *HttpClient) AllowInsecure(insecure bool) {
	if insecure {
		tr := &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}

		self.client.Transport = tr
	} else {
		self.client.Transport = nil
	}
}

//
// add default headers plus extra headers
//
func (self *HttpClient) addHeaders(req *http.Request, headers map[string]string) {

	if len(self.UserAgent) > 0 {
		req.Header.Set("User-Agent", self.UserAgent)
	}

	for k, v := range self.Headers {
		req.Header.Set(k, v)
	}

	for k, v := range headers {
		req.Header.Set(k, v)
	}
}

//
// the callback for CheckRedirect, used to pass along the headers in case of redirection
//
func (self *HttpClient) checkRedirect(req *http.Request, via []*http.Request) error {
	if self.Verbose {
		log.Println("REDIRECT:", len(via), req.URL)
	}

	if len(via) >= 10 {
		return errors.New("stopped after 10 redirects")
	}

	// TODO: check for same host before adding headers
	self.addHeaders(req, nil)
	return nil
}

//
// Create a request object given the method, path, body and extra headers
//
func (self *HttpClient) Request(method string, urlpath string, body io.Reader, headers map[string]string) (req *http.Request) {
	if u, err := self.BaseURL.Parse(urlpath); err != nil {
		log.Fatal(err)
	} else {
		urlpath = u.String()
	}

	req, err := http.NewRequest(method, urlpath, body)
	if err != nil {
		log.Fatal(err)
	}

	self.addHeaders(req, headers)
	return
}

//
// Execute request
//
func (self *HttpClient) Do(req *http.Request) (*HttpResponse, error) {
	if self.Verbose {
		log.Println("REQUEST:", req.Method, req.URL, pretty.PrettyFormat(req.Header))
	}

	resp, err := self.client.Do(req)
	if err == nil {
		if self.Verbose {
			log.Println("RESPONSE:", resp.Status, pretty.PrettyFormat(resp.Header))
		}

		return &HttpResponse{*resp}, nil
	} else {
		if self.Verbose {
			log.Println("ERROR:", err)
		}

		return nil, err
	}
}

//
// Execute a DELETE request
//
func (self *HttpClient) Delete(path string, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("DELETE", path, nil, headers)
	return self.Do(req)
}

//
// Execute a HEAD request
//
func (self *HttpClient) Head(path string, params map[string]interface{}, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("HEAD", URLWithParams(path, params).String(), nil, headers)
	return self.Do(req)
}

//
// Execute a GET request
//
func (self *HttpClient) Get(path string, params map[string]interface{}, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("GET", URLWithParams(path, params).String(), nil, headers)
	return self.Do(req)
}

//
// Execute a POST request
//
func (self *HttpClient) Post(path string, content io.Reader, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("POST", path, content, headers)
	if headers != nil {
		for k, v := range headers {
			req.Header.Set(k, v)
		}
	}
	return self.Do(req)
}
<|endoftext|>


<commit_before>// Package livelog provides a simple wrapper around the livelog executable.
// After the generic worker is refactored into engines, plugins and a runtime,
// livelog will be a plugin instead.
package livelog

import (
	"fmt"
	"io"
	"net"
	"net/http"
	"os"
	"os/exec"
	"strconv"
	"time"

	"github.com/taskcluster/slugid-go/slugid"
)

// LiveLog provides access to a livelog process running on the OS. Use
// New(liveLogExecutable string) to start a new livelog instance.
type LiveLog struct {
	sslCert string
	sslKey  string
	secret  string
	command *exec.Cmd
	PUTPort uint16
	GETPort uint16
	putURL  string
	// The fully qualified HTTP GET URL where the log will be published.
	GetURL    string
	logReader io.ReadCloser
	// The io.WriteCloser to write your log to
	LogWriter io.WriteCloser
}

// New starts a livelog OS process using the executable specified, and returns
// a *LiveLog. The *LiveLog provides access to the GetURL which can be used to
// tail the log by multiple consumers in parallel, together with an
// io.WriteCloser where the logs should be written to. It is envisanged that
// the io.WriteCloser is passed on to the executing process.
//
// sslCert and sslKey should be used to specify the file location of a
// suitable certificate and key on the local filesystem that can be used
// for hosting the livelog service over https. If either is an empty string
// the livelog will resort to running over http transport instead.
//
// Please note the GetURL is for the loopback interface - it is beyond the
// scope of this library to transform this localhost URL into a URL with a
// fully qualified hostname using package
// github.com/taskcluster/stateless-dns-go/hostname since this package can be
// used independently of the former one.
func New(liveLogExecutable, sslCert, sslKey string, putPort, getPort uint16) (*LiveLog, error) {
	l := &LiveLog{
		secret:  slugid.Nice(),
		command: exec.Command(liveLogExecutable),
		sslCert: sslCert,
		sslKey:  sslKey,
		PUTPort: putPort,
		GETPort: getPort,
	}
	// os.Setenv("ACCESS_TOKEN", l.secret)
	os.Setenv("LIVELOG_GET_PORT", strconv.Itoa(int(l.GETPort)))
	os.Setenv("LIVELOG_PUT_PORT", strconv.Itoa(int(l.PUTPort)))
	// os.Setenv("SERVER_CRT_FILE", l.sslCert)
	// os.Setenv("SERVER_KEY_FILE", l.sslKey)
	l.command.Env = os.Environ()
	err := l.command.Start()
	// TODO: we need to make sure that this livelog process we just started
	// doesn't just exit, which can happen if the port is already in use!!!
	// Note, this is really bad, since another livelog will use a different
	// secret. Also note we get a 0 exit code when process exits because
	// another process was listening on the port(s).
	if err != nil {
		return nil, err
	}
	l.setRequestURLs()
	err = l.connectInputStream()
	// Note we can't wait for GET port to be active before returning since
	// livelog will only serve from that port once some content is sent - so no
	// good to execute waitForPortToBeActive(l.getPort) here...  We would need
	// to fix this in livelog codebase not here...
	return l, err
}

// Terminate will close the log writer, and then kill the livelog system
// process.
func (l *LiveLog) Terminate() error {
	// DON'T close the reader!!! otherwise PUT will fail
	// i.e DON'T write `l.logReader.Close()`
	l.LogWriter.Close()
	return l.command.Process.Kill()
}

func (l *LiveLog) setRequestURLs() {
	scheme := "http"
	if os.Getenv("SERVER_CRT_FILE") != "" && os.Getenv("SERVER_KEY_FILE") != "" {
		scheme = "https"
	}
	l.putURL = fmt.Sprintf("%v://localhost:%v/log", scheme, l.PUTPort)
	l.GetURL = fmt.Sprintf("%v://localhost:%v/log/%v", scheme, l.GETPort, l.secret)
}

func (l *LiveLog) connectInputStream() error {
	l.logReader, l.LogWriter = io.Pipe()
	req, err := http.NewRequest("PUT", l.putURL, l.logReader)
	if err != nil {
		return err
	}
	client := new(http.Client)
	go func() {
		// We need to wait until put port is opened which is some time after the
		// livelog process has started...
		waitForPortToBeActive(l.PUTPort)
		// since we waited so long, maybe livelog service isn't running now, so
		// ignore any error and response we get back...
		client.Do(req)
	}()
	return nil
}

func waitForPortToBeActive(port uint16) {
	deadline := time.Now().Add(60 * time.Second)
	for time.Now().Before(deadline) {
		conn, err := net.DialTimeout("tcp", "localhost:"+strconv.Itoa(int(port)), 60*time.Second)
		if err != nil {
			time.Sleep(100 * time.Millisecond)
		} else {
			_ = conn.Close()
			break
		}
	}
}
<commit_msg>Oh god<commit_after>// Package livelog provides a simple wrapper around the livelog executable.
// After the generic worker is refactored into engines, plugins and a runtime,
// livelog will be a plugin instead.
package livelog

import (
	"fmt"
	"io"
	"net"
	"net/http"
	"os"
	"os/exec"
	"strconv"
	"time"

	"github.com/taskcluster/slugid-go/slugid"
)

// LiveLog provides access to a livelog process running on the OS. Use
// New(liveLogExecutable string) to start a new livelog instance.
type LiveLog struct {
	sslCert string
	sslKey  string
	secret  string
	command *exec.Cmd
	PUTPort uint16
	GETPort uint16
	putURL  string
	// The fully qualified HTTP GET URL where the log will be published.
	GetURL    string
	logReader io.ReadCloser
	// The io.WriteCloser to write your log to
	LogWriter io.WriteCloser
}

// New starts a livelog OS process using the executable specified, and returns
// a *LiveLog. The *LiveLog provides access to the GetURL which can be used to
// tail the log by multiple consumers in parallel, together with an
// io.WriteCloser where the logs should be written to. It is envisanged that
// the io.WriteCloser is passed on to the executing process.
//
// sslCert and sslKey should be used to specify the file location of a
// suitable certificate and key on the local filesystem that can be used
// for hosting the livelog service over https. If either is an empty string
// the livelog will resort to running over http transport instead.
//
// Please note the GetURL is for the loopback interface - it is beyond the
// scope of this library to transform this localhost URL into a URL with a
// fully qualified hostname using package
// github.com/taskcluster/stateless-dns-go/hostname since this package can be
// used independently of the former one.
func New(liveLogExecutable, sslCert, sslKey string, putPort, getPort uint16) (*LiveLog, error) {
	l := &LiveLog{
		secret:  slugid.Nice(),
		command: exec.Command(liveLogExecutable),
		sslCert: sslCert,
		sslKey:  sslKey,
		PUTPort: putPort,
		GETPort: getPort,
	}
	l.command.Env = append(
		os.Environ(),
		"ACCESS_TOKEN="+l.secret,
		"LIVELOG_GET_PORT="+strconv.Itoa(int(l.GETPort)),
		"LIVELOG_PUT_PORT="+strconv.Itoa(int(l.PUTPort)),
		"SERVER_CRT_FILE="+l.sslCert,
		"SERVER_KEY_FILE="+l.sslKey,
	)
	err := l.command.Start()
	// TODO: we need to make sure that this livelog process we just started
	// doesn't just exit, which can happen if the port is already in use!!!
	// Note, this is really bad, since another livelog will use a different
	// secret. Also note we get a 0 exit code when process exits because
	// another process was listening on the port(s).
	if err != nil {
		return nil, err
	}
	l.setRequestURLs()
	err = l.connectInputStream()
	// Note we can't wait for GET port to be active before returning since
	// livelog will only serve from that port once some content is sent - so no
	// good to execute waitForPortToBeActive(l.getPort) here...  We would need
	// to fix this in livelog codebase not here...
	return l, err
}

// Terminate will close the log writer, and then kill the livelog system
// process.
func (l *LiveLog) Terminate() error {
	// DON'T close the reader!!! otherwise PUT will fail
	// i.e DON'T write `l.logReader.Close()`
	l.LogWriter.Close()
	return l.command.Process.Kill()
}

func (l *LiveLog) setRequestURLs() {
	scheme := "http"
	if os.Getenv("SERVER_CRT_FILE") != "" && os.Getenv("SERVER_KEY_FILE") != "" {
		scheme = "https"
	}
	l.putURL = fmt.Sprintf("%v://localhost:%v/log", scheme, l.PUTPort)
	l.GetURL = fmt.Sprintf("%v://localhost:%v/log/%v", scheme, l.GETPort, l.secret)
}

func (l *LiveLog) connectInputStream() error {
	l.logReader, l.LogWriter = io.Pipe()
	req, err := http.NewRequest("PUT", l.putURL, l.logReader)
	if err != nil {
		return err
	}
	client := new(http.Client)
	go func() {
		// We need to wait until put port is opened which is some time after the
		// livelog process has started...
		waitForPortToBeActive(l.PUTPort)
		// since we waited so long, maybe livelog service isn't running now, so
		// ignore any error and response we get back...
		client.Do(req)
	}()
	return nil
}

func waitForPortToBeActive(port uint16) {
	deadline := time.Now().Add(60 * time.Second)
	for time.Now().Before(deadline) {
		conn, err := net.DialTimeout("tcp", "localhost:"+strconv.Itoa(int(port)), 60*time.Second)
		if err != nil {
			time.Sleep(100 * time.Millisecond)
		} else {
			_ = conn.Close()
			break
		}
	}
}
<|endoftext|>


<commit_before>package stddata
// Keep reading: http://golang.org/doc/code.html#Testing
import (
	"fmt"
	"testing"
)

func TestLoadFpddir(t *testing.T) {
	fmt.Println("Test: LoadFpddir")
	LoadFpddir()
	// check count or something.
}
<commit_msg>organized into packages step 1/2<commit_after><|endoftext|>


<commit_before>package client

import (
	"errors"
	"fmt"
	"os"
	"time"

	"golang.org/x/net/context"

	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"

	"github.com/pachyderm/pachyderm/src/client/health"
	"github.com/pachyderm/pachyderm/src/client/pfs"
	"github.com/pachyderm/pachyderm/src/client/pkg/config"
	"github.com/pachyderm/pachyderm/src/client/pps"

	google_protobuf "go.pedge.io/pb/go/google/protobuf"
)

// PfsAPIClient is an alias for pfs.APIClient.
type PfsAPIClient pfs.APIClient

// PpsAPIClient is an alias for pps.APIClient.
type PpsAPIClient pps.APIClient

// BlockAPIClient is an alias for pfs.BlockAPIClient.
type BlockAPIClient pfs.BlockAPIClient

// An APIClient is a wrapper around pfs, pps and block APIClients.
type APIClient struct {
	PfsAPIClient
	PpsAPIClient
	BlockAPIClient
	addr              string
	clientConn        *grpc.ClientConn
	healthClient      health.HealthClient
	_ctx              context.Context
	config            *config.Config
	cancel            func()
	reportUserMetrics bool
}

// Creates a client that will report User Metrics
func NewUserClientFromAddress(addr string) (*APIClient, error) {
	c, err := NewFromAddress(addr)
	if c != nil {
		c.reportUserMetrics = true
	}
	return c, err
}

// NewFromAddress constructs a new APIClient for the server at addr.
func NewFromAddress(addr string) (*APIClient, error) {
	cfg, err := config.Read()
	if err != nil {
		return nil, err
	}
	c := &APIClient{
		addr:   addr,
		config: cfg,
	}
	if err := c.connect(); err != nil {
		return nil, err
	}
	return c, nil
}

// NewInCluster constructs a new APIClient using env vars that Kubernetes creates.
// This should be used to access Pachyderm from within a Kubernetes cluster
// with Pachyderm running on it.
func NewInCluster() (*APIClient, error) {
	addr := os.Getenv("PACHD_PORT_650_TCP_ADDR")

	if addr == "" {
		return nil, fmt.Errorf("PACHD_PORT_650_TCP_ADDR not set")
	}

	return NewFromAddress(fmt.Sprintf("%v:650", addr))
}

// Close the connection to gRPC
func (c *APIClient) Close() error {
	return c.clientConn.Close()
}

// KeepConnected periodically health checks the connection and attempts to
// reconnect if it becomes unhealthy.
func (c *APIClient) KeepConnected(cancel chan bool) {
	for {
		select {
		case <-cancel:
			return
		default:
			ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
			defer cancel()
			if _, err := c.healthClient.Health(ctx, google_protobuf.EmptyInstance); err != nil {
				c.cancel()
				c.connect()
			}
		}
	}
}

// DeleteAll deletes everything in the cluster.
// Use with caution, there is no undo.
func (c APIClient) DeleteAll() error {
	if _, err := c.PpsAPIClient.DeleteAll(
		c.ctx(),
		google_protobuf.EmptyInstance,
	); err != nil {
		return sanitizeErr(err)
	}
	if _, err := c.PfsAPIClient.DeleteAll(
		c.ctx(),
		google_protobuf.EmptyInstance,
	); err != nil {
		return sanitizeErr(err)
	}
	return nil
}

func (c *APIClient) connect() error {
	clientConn, err := grpc.Dial(c.addr, grpc.WithInsecure())
	if err != nil {
		return err
	}

	ctx, cancel := context.WithCancel(context.Background())

	c.PfsAPIClient = pfs.NewAPIClient(clientConn)
	c.PpsAPIClient = pps.NewAPIClient(clientConn)
	c.BlockAPIClient = pfs.NewBlockAPIClient(clientConn)
	c.clientConn = clientConn
	c.healthClient = health.NewHealthClient(clientConn)
	c._ctx = ctx
	c.cancel = cancel
	return nil
}

func (c *APIClient) addMetadata(ctx context.Context) context.Context {
	if !c.reportUserMetrics {
		return ctx
	}
	if c.config == nil {
		// Don't report error if config fails to read. This is only needed for metrics
		// We don't want to err if metrics reporting is the only thing that breaks
		cfg, _ := config.Read()
		c.config = cfg
	}
	return metadata.NewContext(
		ctx,
		metadata.Pairs("UserID", c.config.UserID),
	)
}

func (c *APIClient) ctx() context.Context {
	if c._ctx == nil {
		return c.addMetadata(context.Background())
	}
	return c.addMetadata(c._ctx)
}

func sanitizeErr(err error) error {
	if err == nil {
		return nil
	}

	return errors.New(grpc.ErrorDesc(err))
}
<commit_msg>Clean up NewUserClientFromAddress()<commit_after>package client

import (
	"errors"
	"fmt"
	"os"
	"time"

	"golang.org/x/net/context"

	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"

	"github.com/pachyderm/pachyderm/src/client/health"
	"github.com/pachyderm/pachyderm/src/client/pfs"
	"github.com/pachyderm/pachyderm/src/client/pkg/config"
	"github.com/pachyderm/pachyderm/src/client/pps"

	google_protobuf "go.pedge.io/pb/go/google/protobuf"
)

// PfsAPIClient is an alias for pfs.APIClient.
type PfsAPIClient pfs.APIClient

// PpsAPIClient is an alias for pps.APIClient.
type PpsAPIClient pps.APIClient

// BlockAPIClient is an alias for pfs.BlockAPIClient.
type BlockAPIClient pfs.BlockAPIClient

// An APIClient is a wrapper around pfs, pps and block APIClients.
type APIClient struct {
	PfsAPIClient
	PpsAPIClient
	BlockAPIClient
	addr              string
	clientConn        *grpc.ClientConn
	healthClient      health.HealthClient
	_ctx              context.Context
	config            *config.Config
	cancel            func()
	reportUserMetrics bool
}

// Creates a client that will report User Metrics
func NewUserClientFromAddress(addr string) (*APIClient, error) {
	c, err := NewFromAddress(addr)
	if err != nil {
		return nil, err
	}
	c.reportUserMetrics = true
	cfg, err := config.Read()
	if err != nil {
		return nil, err
	}
	c.config = cfg
	return c, err
}

// NewFromAddress constructs a new APIClient for the server at addr.
func NewFromAddress(addr string) (*APIClient, error) {
	c := &APIClient{
		addr: addr,
	}
	if err := c.connect(); err != nil {
		return nil, err
	}
	return c, nil
}

// NewInCluster constructs a new APIClient using env vars that Kubernetes creates.
// This should be used to access Pachyderm from within a Kubernetes cluster
// with Pachyderm running on it.
func NewInCluster() (*APIClient, error) {
	addr := os.Getenv("PACHD_PORT_650_TCP_ADDR")

	if addr == "" {
		return nil, fmt.Errorf("PACHD_PORT_650_TCP_ADDR not set")
	}

	return NewFromAddress(fmt.Sprintf("%v:650", addr))
}

// Close the connection to gRPC
func (c *APIClient) Close() error {
	return c.clientConn.Close()
}

// KeepConnected periodically health checks the connection and attempts to
// reconnect if it becomes unhealthy.
func (c *APIClient) KeepConnected(cancel chan bool) {
	for {
		select {
		case <-cancel:
			return
		default:
			ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
			defer cancel()
			if _, err := c.healthClient.Health(ctx, google_protobuf.EmptyInstance); err != nil {
				c.cancel()
				c.connect()
			}
		}
	}
}

// DeleteAll deletes everything in the cluster.
// Use with caution, there is no undo.
func (c APIClient) DeleteAll() error {
	if _, err := c.PpsAPIClient.DeleteAll(
		c.ctx(),
		google_protobuf.EmptyInstance,
	); err != nil {
		return sanitizeErr(err)
	}
	if _, err := c.PfsAPIClient.DeleteAll(
		c.ctx(),
		google_protobuf.EmptyInstance,
	); err != nil {
		return sanitizeErr(err)
	}
	return nil
}

func (c *APIClient) connect() error {
	clientConn, err := grpc.Dial(c.addr, grpc.WithInsecure())
	if err != nil {
		return err
	}

	ctx, cancel := context.WithCancel(context.Background())

	c.PfsAPIClient = pfs.NewAPIClient(clientConn)
	c.PpsAPIClient = pps.NewAPIClient(clientConn)
	c.BlockAPIClient = pfs.NewBlockAPIClient(clientConn)
	c.clientConn = clientConn
	c.healthClient = health.NewHealthClient(clientConn)
	c._ctx = ctx
	c.cancel = cancel
	return nil
}

func (c *APIClient) addMetadata(ctx context.Context) context.Context {
	if !c.reportUserMetrics {
		return ctx
	}
	if c.config == nil {
		// Don't report error if config fails to read. This is only needed for metrics
		// We don't want to err if metrics reporting is the only thing that breaks
		cfg, _ := config.Read()
		c.config = cfg
	}
	return metadata.NewContext(
		ctx,
		metadata.Pairs("UserID", c.config.UserID),
	)
}

func (c *APIClient) ctx() context.Context {
	if c._ctx == nil {
		return c.addMetadata(context.Background())
	}
	return c.addMetadata(c._ctx)
}

func sanitizeErr(err error) error {
	if err == nil {
		return nil
	}

	return errors.New(grpc.ErrorDesc(err))
}
<|endoftext|>