From b7794a878303c2a569b4c1c4d830728a2357f946 Mon Sep 17 00:00:00 2001 From: John Guo Date: Tue, 19 Jul 2022 16:30:00 +0800 Subject: [PATCH] use method name as its command name if no name defined in Meta of input struct for package gcmd (#2019) --- os/gcmd/gcmd_command_object.go | 87 ++++++++++----------- os/gcmd/gcmd_z_unit_feature_object4_test.go | 49 ++++++++++++ os/gcron/gcron_z_unit_test.go | 13 +++ 3 files changed, 104 insertions(+), 45 deletions(-) create mode 100644 os/gcmd/gcmd_z_unit_feature_object4_test.go diff --git a/os/gcmd/gcmd_command_object.go b/os/gcmd/gcmd_command_object.go index 2dbeb2489..c1f6b29ab 100644 --- a/os/gcmd/gcmd_command_object.go +++ b/os/gcmd/gcmd_command_object.go @@ -28,9 +28,9 @@ import ( ) const ( - tagNameDc = `dc` - tagNameAd = `ad` - tagNameEg = `eg` + tagNameDc = `dc` // description. + tagNameAd = `ad` // additional + tagNameEg = `eg` // examples. tagNameArg = `arg` tagNameRoot = `root` ) @@ -61,7 +61,7 @@ func NewFromObject(object interface{}) (rootCmd *Command, err error) { } // Root command creating. - rootCmd, err = newCommandFromObjectMeta(object) + rootCmd, err = newCommandFromObjectMeta(object, "") if err != nil { return } @@ -76,17 +76,19 @@ func NewFromObject(object interface{}) (rootCmd *Command, err error) { } for i := 0; i < reflectValue.NumMethod(); i++ { var ( - method = reflectValue.Method(i) - methodCmd *Command + method = reflectValue.Type().Method(i) + methodValue = reflectValue.Method(i) + methodType = methodValue.Type() + methodCmd *Command ) - methodCmd, err = newCommandFromMethod(object, method) + methodCmd, err = newCommandFromMethod(object, method, methodValue, methodType) if err != nil { return } if nameSet.Contains(methodCmd.Name) { err = gerror.Newf( `command name should be unique, found duplicated command name in method "%s"`, - method.Type().String(), + methodType.String(), ) return } @@ -135,27 +137,23 @@ func methodToRootCmdWhenNameEqual(rootCmd *Command, methodCmd *Command) { } } -func newCommandFromObjectMeta(object interface{}) (command *Command, err error) { - var ( - metaData = gmeta.Data(object) - ) - if len(metaData) == 0 { - err = gerror.Newf( - `no meta data found in struct "%s"`, - reflect.TypeOf(object).String(), - ) - return - } +// The `object` is the Meta attribute from business object, and the `name` is the command name, +// commonly from method name, which is used when no name tag is defined in Meta. +func newCommandFromObjectMeta(object interface{}, name string) (command *Command, err error) { + var metaData = gmeta.Data(object) if err = gconv.Scan(metaData, &command); err != nil { return } // Name filed is necessary. if command.Name == "" { - err = gerror.Newf( - `command name cannot be empty, "name" tag not found in meta of struct "%s"`, - reflect.TypeOf(object).String(), - ) - return + if name == "" { + err = gerror.Newf( + `command name cannot be empty, "name" tag not found in meta of struct "%s"`, + reflect.TypeOf(object).String(), + ) + return + } + command.Name = name } if command.Description == "" { command.Description = metaData[tagNameDc] @@ -169,71 +167,70 @@ func newCommandFromObjectMeta(object interface{}) (command *Command, err error) return } -func newCommandFromMethod(object interface{}, method reflect.Value) (command *Command, err error) { - var ( - reflectType = method.Type() - ) +func newCommandFromMethod( + object interface{}, method reflect.Method, methodValue reflect.Value, methodType reflect.Type, +) (command *Command, err error) { // Necessary validation for input/output parameters and naming. - if reflectType.NumIn() != 2 || reflectType.NumOut() != 2 { - if reflectType.PkgPath() != "" { + if methodType.NumIn() != 2 || methodType.NumOut() != 2 { + if methodType.PkgPath() != "" { err = gerror.NewCodef( gcode.CodeInvalidParameter, `invalid command: %s.%s.%s defined as "%s", but "func(context.Context, Input)(Output, error)" is required`, - reflectType.PkgPath(), reflect.TypeOf(object).Name(), reflectType.Name(), reflectType.String(), + methodType.PkgPath(), reflect.TypeOf(object).Name(), methodType.Name(), methodType.String(), ) } else { err = gerror.NewCodef( gcode.CodeInvalidParameter, `invalid command: defined as "%s", but "func(context.Context, Input)(Output, error)" is required`, - reflectType.String(), + methodType.String(), ) } return } - if reflectType.In(0).String() != "context.Context" { + if methodType.In(0).String() != "context.Context" { err = gerror.NewCodef( gcode.CodeInvalidParameter, `invalid command: defined as "%s", but the first input parameter should be type of "context.Context"`, - reflectType.String(), + methodType.String(), ) return } - if reflectType.Out(1).String() != "error" { + if methodType.Out(1).String() != "error" { err = gerror.NewCodef( gcode.CodeInvalidParameter, `invalid command: defined as "%s", but the last output parameter should be type of "error"`, - reflectType.String(), + methodType.String(), ) return } // The input struct should be named as `xxxInput`. - if !gstr.HasSuffix(reflectType.In(1).String(), `Input`) { + if !gstr.HasSuffix(methodType.In(1).String(), `Input`) { err = gerror.NewCodef( gcode.CodeInvalidParameter, `invalid struct naming for input: defined as "%s", but it should be named with "Input" suffix like "xxxInput"`, - reflectType.In(1).String(), + methodType.In(1).String(), ) return } // The output struct should be named as `xxxOutput`. - if !gstr.HasSuffix(reflectType.Out(0).String(), `Output`) { + if !gstr.HasSuffix(methodType.Out(0).String(), `Output`) { err = gerror.NewCodef( gcode.CodeInvalidParameter, `invalid struct naming for output: defined as "%s", but it should be named with "Output" suffix like "xxxOutput"`, - reflectType.Out(0).String(), + methodType.Out(0).String(), ) return } var inputObject reflect.Value - if method.Type().In(1).Kind() == reflect.Ptr { - inputObject = reflect.New(method.Type().In(1).Elem()).Elem() + if methodType.In(1).Kind() == reflect.Ptr { + inputObject = reflect.New(methodType.In(1).Elem()).Elem() } else { - inputObject = reflect.New(method.Type().In(1)).Elem() + inputObject = reflect.New(methodType.In(1)).Elem() } // Command creating. - if command, err = newCommandFromObjectMeta(inputObject.Interface()); err != nil { + if command, err = newCommandFromObjectMeta(inputObject.Interface(), method.Name); err != nil { return } @@ -312,7 +309,7 @@ func newCommandFromMethod(object interface{}, method reflect.Value) (command *Co inputValues = append(inputValues, inputObject) // Call handler with dynamic created parameter values. - results := method.Call(inputValues) + results := methodValue.Call(inputValues) out = results[0].Interface() if !results[1].IsNil() { if v, ok := results[1].Interface().(error); ok { diff --git a/os/gcmd/gcmd_z_unit_feature_object4_test.go b/os/gcmd/gcmd_z_unit_feature_object4_test.go new file mode 100644 index 000000000..c94456609 --- /dev/null +++ b/os/gcmd/gcmd_z_unit_feature_object4_test.go @@ -0,0 +1,49 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gcmd_test + +import ( + "context" + "os" + "testing" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/test/gtest" +) + +type TestNoNameTagCase struct { + g.Meta `name:"root"` +} + +type TestNoNameTagCaseRootInput struct { + Name string +} +type TestNoNameTagCaseRootOutput struct { + Content string +} + +func (c *TestNoNameTagCase) TEST(ctx context.Context, in TestNoNameTagCaseRootInput) (out *TestNoNameTagCaseRootOutput, err error) { + out = &TestNoNameTagCaseRootOutput{ + Content: in.Name, + } + return +} + +func Test_Command_NoNameTagCase(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var ctx = gctx.New() + cmd, err := gcmd.NewFromObject(TestNoNameTagCase{}) + t.AssertNil(err) + + os.Args = []string{"root", "TEST", "-name=john"} + value, err := cmd.RunWithValueError(ctx) + t.AssertNil(err) + t.Assert(value, `{"Content":"john"}`) + }) +} diff --git a/os/gcron/gcron_z_unit_test.go b/os/gcron/gcron_z_unit_test.go index 6a7d31a65..ca4f70d12 100644 --- a/os/gcron/gcron_z_unit_test.go +++ b/os/gcron/gcron_z_unit_test.go @@ -88,6 +88,17 @@ func TestCron_Remove(t *testing.T) { } func TestCron_Add_FixedPattern(t *testing.T) { + //debug := utils.IsDebugEnabled() + //utils.SetDebugEnabled(true) + //defer func() { + // utils.SetDebugEnabled(debug) + //}() + for i := 0; i < 5; i++ { + doTestCronAddFixedPattern(t) + } +} + +func doTestCronAddFixedPattern(t *testing.T) { gtest.C(t, func(t *gtest.T) { var ( now = time.Now() @@ -96,6 +107,8 @@ func TestCron_Add_FixedPattern(t *testing.T) { minutes = now.Minute() seconds = now.Second() + 2 ) + defer cron.Close() + if seconds >= 60 { seconds %= 60 minutes++