Skip to content

Commit

Permalink
use method name as its command name if no name defined in Meta of inp…
Browse files Browse the repository at this point in the history
…ut struct for package gcmd (#2019)
  • Loading branch information
gqcn authored Jul 19, 2022
1 parent bb3c51c commit b7794a8
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 45 deletions.
87 changes: 42 additions & 45 deletions os/gcmd/gcmd_command_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -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`
)
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down Expand Up @@ -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]
Expand All @@ -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
}

Expand Down Expand Up @@ -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 {
Expand Down
49 changes: 49 additions & 0 deletions os/gcmd/gcmd_z_unit_feature_object4_test.go
Original file line number Diff line number Diff line change
@@ -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"}`)
})
}
13 changes: 13 additions & 0 deletions os/gcron/gcron_z_unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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++
Expand Down

0 comments on commit b7794a8

Please sign in to comment.