fix commands issues (#492)

* fix api commands output

* remove unused code

* fix convert always has -r

* update merge err to locate failed file
This commit is contained in:
Jebbs 2020-12-20 13:35:52 +08:00 committed by Shelikhoo
parent 12dfbc78a3
commit ec1694beb1
No known key found for this signature in database
GPG Key ID: C4D5E79D22B25316
4 changed files with 197 additions and 16 deletions

View File

@ -18,6 +18,7 @@ package merge
import (
"bytes"
"encoding/json"
"fmt"
"io"
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
@ -73,14 +74,14 @@ func loadFiles(args []string) (map[string]interface{}, error) {
for _, arg := range args {
r, err := cmdarg.LoadArg(arg)
if err != nil {
return nil, err
return nil, fmt.Errorf("fail to load %s: %s", arg, err)
}
m, err := decode(r)
if err != nil {
return nil, err
return nil, fmt.Errorf("fail to decode %s: %s", arg, err)
}
if err = mergeMaps(c, m); err != nil {
return nil, err
return nil, fmt.Errorf("fail to merge %s: %s", arg, err)
}
}
return c, nil

View File

@ -2,7 +2,10 @@ package api
import (
"context"
"encoding/json"
"fmt"
"os"
"reflect"
"strings"
"time"
@ -11,8 +14,6 @@ import (
"google.golang.org/protobuf/proto"
)
type serviceHandler func(ctx context.Context, conn *grpc.ClientConn, cmd *base.Command, args []string) string
var (
apiServerAddrPtr string
apiTimeout int
@ -39,16 +40,55 @@ func dialAPIServer() (conn *grpc.ClientConn, ctx context.Context, close func())
}
func showResponese(m proto.Message) {
msg := ""
bs, err := proto.Marshal(m)
if err != nil {
msg = err.Error()
} else {
msg = string(bs)
msg = strings.TrimSpace(msg)
}
if msg == "" {
if isEmpty(m) {
// avoid outputs like `{}`, `{"key":{}}`
return
}
fmt.Println(msg)
b := new(strings.Builder)
e := json.NewEncoder(b)
e.SetIndent("", " ")
e.SetEscapeHTML(false)
err := e.Encode(m)
if err != nil {
fmt.Fprintf(os.Stdout, "%v\n", m)
base.Fatalf("error encode json: %s", err)
return
}
fmt.Println(strings.TrimSpace(b.String()))
}
// isEmpty checks if the response is empty (all zero values).
// proto.Message types always "omitempty" on fields,
// there's no chance for a response to show zero-value messages,
// so we can perform isZero test here
func isEmpty(response interface{}) bool {
s := reflect.Indirect(reflect.ValueOf(response))
if s.Kind() == reflect.Invalid {
return true
}
switch s.Kind() {
case reflect.Struct:
for i := 0; i < s.NumField(); i++ {
f := s.Type().Field(i)
if f.Name[0] < 65 || f.Name[0] > 90 {
// continue if not exported.
continue
}
field := s.Field(i)
if !isEmpty(field.Interface()) {
return false
}
}
case reflect.Array, reflect.Slice:
for i := 0; i < s.Len(); i++ {
if !isEmpty(s.Index(i).Interface()) {
return false
}
}
default:
if !s.IsZero() {
return false
}
}
return true
}

View File

@ -0,0 +1,140 @@
package api
import (
"testing"
statsService "v2ray.com/core/app/stats/command"
)
func TestEmptyResponese_0(t *testing.T) {
r := &statsService.QueryStatsResponse{
Stat: []*statsService.Stat{
{
Name: "1>>2",
Value: 1,
},
{
Name: "1>>2>>3",
Value: 2,
},
},
}
assert(t, isEmpty(r), false)
}
func TestEmptyResponese_1(t *testing.T) {
r := (*statsService.QueryStatsResponse)(nil)
assert(t, isEmpty(r), true)
}
func TestEmptyResponese_2(t *testing.T) {
r := &statsService.QueryStatsResponse{
Stat: nil,
}
assert(t, isEmpty(r), true)
}
func TestEmptyResponese_3(t *testing.T) {
r := &statsService.QueryStatsResponse{
Stat: []*statsService.Stat{},
}
assert(t, isEmpty(r), true)
}
func TestEmptyResponese_4(t *testing.T) {
r := &statsService.QueryStatsResponse{
Stat: []*statsService.Stat{
{
Name: "",
Value: 0,
},
},
}
assert(t, isEmpty(r), true)
}
func TestEmptyResponese_5(t *testing.T) {
type test struct {
Value *statsService.QueryStatsResponse
}
r := &test{
Value: &statsService.QueryStatsResponse{
Stat: []*statsService.Stat{
{
Name: "",
},
},
},
}
assert(t, isEmpty(r), true)
}
func TestEmptyResponese_6(t *testing.T) {
type test struct {
Value *statsService.QueryStatsResponse
}
r := &test{
Value: &statsService.QueryStatsResponse{
Stat: []*statsService.Stat{
{
Value: 1,
},
},
},
}
assert(t, isEmpty(r), false)
}
func TestEmptyResponese_7(t *testing.T) {
type test struct {
Value *int
}
v := 1
r := &test{
Value: &v,
}
assert(t, isEmpty(r), false)
}
func TestEmptyResponese_8(t *testing.T) {
type test struct {
Value *int
}
v := 0
r := &test{
Value: &v,
}
assert(t, isEmpty(r), true)
}
func TestEmptyResponese_9(t *testing.T) {
assert(t, isEmpty(0), true)
}
func TestEmptyResponese_10(t *testing.T) {
assert(t, isEmpty(1), false)
}
func TestEmptyResponese_11(t *testing.T) {
r := []*statsService.Stat{
{
Name: "",
},
}
assert(t, isEmpty(r), true)
}
func TestEmptyResponese_12(t *testing.T) {
r := []*statsService.Stat{
{
Value: 1,
},
}
assert(t, isEmpty(r), false)
}
func assert(t *testing.T, value, expected bool) {
if value != expected {
t.Fatalf("Expected: %v, actual: %v", expected, value)
}
}

View File

@ -72,7 +72,7 @@ func setConfArgs(cmd *base.Command) {
cmd.Flag.StringVar(&inputFormat, "i", "json", "")
cmd.Flag.StringVar(&outputFormat, "output", "json", "")
cmd.Flag.StringVar(&outputFormat, "o", "json", "")
cmd.Flag.BoolVar(&confDirRecursively, "r", true, "")
cmd.Flag.BoolVar(&confDirRecursively, "r", false, "")
}
func executeConvert(cmd *base.Command, args []string) {
setConfArgs(cmd)