diff --git a/protocol/goai/goai_parameter.go b/protocol/goai/goai_parameter.go index 561b8c396..23ca40314 100644 --- a/protocol/goai/goai_parameter.go +++ b/protocol/goai/goai_parameter.go @@ -7,6 +7,7 @@ package goai import ( + "fmt" "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/internal/json" @@ -41,7 +42,7 @@ type ParameterRef struct { Value *Parameter } -func (oai *OpenApiV3) newParameterRefWithStructMethod(field *structs.Field, method string) (*ParameterRef, error) { +func (oai *OpenApiV3) newParameterRefWithStructMethod(field *structs.Field, path, method string) (*ParameterRef, error) { var ( tagMap = field.TagMap() parameter = &Parameter{ @@ -58,13 +59,18 @@ func (oai *OpenApiV3) newParameterRefWithStructMethod(field *structs.Field, meth } } if parameter.In == "" { - // Default the parameter input to "query" if method is "GET/DELETE". - switch gstr.ToUpper(method) { - case HttpMethodGet, HttpMethodDelete: - parameter.In = ParameterInQuery + // Automatically detect its "in" attribute. + if gstr.ContainsI(path, fmt.Sprintf(`{%s}`, parameter.Name)) { + parameter.In = ParameterInPath + } else { + // Default the parameter input to "query" if method is "GET/DELETE". + switch gstr.ToUpper(method) { + case HttpMethodGet, HttpMethodDelete: + parameter.In = ParameterInQuery - default: - return nil, nil + default: + return nil, nil + } } } diff --git a/protocol/goai/goai_path.go b/protocol/goai/goai_path.go index 03dea2c8a..cd1346761 100644 --- a/protocol/goai/goai_path.go +++ b/protocol/goai/goai_path.go @@ -180,7 +180,7 @@ func (oai *OpenApiV3) addPath(in addPathInput) error { Value: &requestBody, } } - // Request parameters. + // It also sets request parameters. structFields, _ := structs.Fields(structs.FieldsInput{ Pointer: inputObject.Interface(), RecursiveOption: structs.RecursiveOptionEmbeddedNoTag, @@ -189,7 +189,7 @@ func (oai *OpenApiV3) addPath(in addPathInput) error { if operation.Parameters == nil { operation.Parameters = []ParameterRef{} } - parameterRef, err := oai.newParameterRefWithStructMethod(structField, in.Method) + parameterRef, err := oai.newParameterRefWithStructMethod(structField, in.Path, in.Method) if err != nil { return err } diff --git a/protocol/goai/goai_test.go b/protocol/goai/goai_test.go index b7a5cd3f2..e079884fb 100644 --- a/protocol/goai/goai_test.go +++ b/protocol/goai/goai_test.go @@ -190,6 +190,50 @@ func TestOpenApiV3_Add_EmptyReqAndRes(t *testing.T) { }) } +func TestOpenApiV3_Add_AutoDetectIn(t *testing.T) { + type Req struct { + gmeta.Meta `method:"get" tags:"default"` + Name string + Product string + Region string + } + + type Res struct { + gmeta.Meta `description:"Demo Response Struct"` + } + + f := func(ctx context.Context, req *Req) (res *Res, err error) { + return + } + + gtest.C(t, func(t *gtest.T) { + var ( + err error + oai = goai.New() + path = `/test/{product}/{name}` + ) + err = oai.Add(goai.AddInput{ + Path: path, + Method: goai.HttpMethodGet, + Object: f, + }) + t.AssertNil(err) + + fmt.Println(oai.String()) + + t.Assert(len(oai.Components.Schemas), 2) + t.Assert(len(oai.Paths), 1) + t.AssertNE(oai.Paths[path].Get, nil) + t.Assert(len(oai.Paths[path].Get.Parameters), 3) + t.Assert(oai.Paths[path].Get.Parameters[0].Value.Name, `Name`) + t.Assert(oai.Paths[path].Get.Parameters[0].Value.In, goai.ParameterInPath) + t.Assert(oai.Paths[path].Get.Parameters[1].Value.Name, `Product`) + t.Assert(oai.Paths[path].Get.Parameters[1].Value.In, goai.ParameterInPath) + t.Assert(oai.Paths[path].Get.Parameters[2].Value.Name, `Region`) + t.Assert(oai.Paths[path].Get.Parameters[2].Value.In, goai.ParameterInQuery) + }) +} + func TestOpenApiV3_CommonRequest(t *testing.T) { type CommonRequest struct { Code int `json:"code" description:"Error code"` diff --git a/util/gvalid/gvalid.go b/util/gvalid/gvalid.go index 4824c9e9b..f1fa06bcd 100644 --- a/util/gvalid/gvalid.go +++ b/util/gvalid/gvalid.go @@ -29,6 +29,7 @@ import ( // required-without-all format: required-without-all:field1,field2,...brief: Required if all given fields are empty. // bail format: bail brief: Stop validating when this field's validation failed. // date format: date brief: Standard date, like: 2006-01-02, 20060102, 2006.01.02 +// datetime format: datetime brief: Standard datetime, like: 2006-01-02 12:00:00 // date-format format: date-format:format brief: Custom date format. // email format: email brief: Email address. // phone format: phone brief: Phone number. @@ -133,6 +134,7 @@ var ( "required-without-all": {}, "bail": {}, "date": {}, + "datetime": {}, "date-format": {}, "email": {}, "phone": {}, @@ -192,6 +194,7 @@ var ( "required-without": "The :attribute field is required", "required-without-all": "The :attribute field is required", "date": "The :attribute value is not a valid date", + "datetime": "The :attribute value is not a valid datetime", "date-format": "The :attribute value does not match the format :format", "email": "The :attribute value must be a valid email address", "phone": "The :attribute value must be a valid phone number", diff --git a/util/gvalid/gvalid_custom_rule.go b/util/gvalid/gvalid_custom_rule.go index aa2fc9ba4..418e9f95f 100644 --- a/util/gvalid/gvalid_custom_rule.go +++ b/util/gvalid/gvalid_custom_rule.go @@ -13,7 +13,7 @@ import "context" // The parameter `rule` specifies the validation rule string, like "required", "between:1,100", etc. // The parameter `value` specifies the value for this rule to validate. // The parameter `message` specifies the custom error message or configured i18n message for this rule. -// The parameter `data` specifies the `data` which is passed to the Validator. It might be type of map/struct or a nil value. +// The parameter `data` specifies the `data` which is passed to the Validator. It might be a type of map/struct or a nil value. // You can ignore the parameter `data` if you do not really need it in your custom validation rule. type RuleFunc func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error diff --git a/util/gvalid/gvalid_validator_check_value.go b/util/gvalid/gvalid_validator_check_value.go index a037de9ab..36176f361 100644 --- a/util/gvalid/gvalid_validator_check_value.go +++ b/util/gvalid/gvalid_validator_check_value.go @@ -258,16 +258,28 @@ func (v *Validator) doCheckBuildInRules(ctx context.Context, input doCheckBuildI } match = gregex.IsMatchString(`\d{4}[\.\-\_/]{0,1}\d{2}[\.\-\_/]{0,1}\d{2}`, valueStr) + // Datetime rule. + case "datetime": + // support for time value, eg: gtime.Time/*gtime.Time, time.Time/*time.Time. + if v, ok := input.Value.(iTime); ok { + return !v.IsZero(), nil + } + if _, err = gtime.StrToTimeFormat(valueStr, `Y-m-d H:i:s`); err == nil { + match = true + } + // Date rule with specified format. case "date-format": // support for time value, eg: gtime.Time/*gtime.Time, time.Time/*time.Time. if v, ok := input.Value.(iTime); ok { return !v.IsZero(), nil } - if _, err := gtime.StrToTimeFormat(valueStr, input.RulePattern); err == nil { + if _, err = gtime.StrToTimeFormat(valueStr, input.RulePattern); err == nil { match = true } else { - var msg string + var ( + msg string + ) msg = v.getErrorMessageByRule(ctx, input.RuleKey, input.CustomMsgMap) msg = strings.Replace(msg, ":format", input.RulePattern, -1) return match, gerror.NewOption(gerror.Option{ diff --git a/util/gvalid/gvalid_z_unit_basic_all_test.go b/util/gvalid/gvalid_z_unit_basic_all_test.go index c50b2f723..83741d477 100755 --- a/util/gvalid/gvalid_z_unit_basic_all_test.go +++ b/util/gvalid/gvalid_z_unit_basic_all_test.go @@ -262,6 +262,27 @@ func Test_Date(t *testing.T) { }) } +func Test_Datetime(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := g.MapStrBool{ + "2010": false, + "2010.11": false, + "2010-11-01": false, + "2010-11-01 12:00": false, + "2010-11-01 12:00:00": true, + "2010.11.01 12:00:00": false, + } + for k, v := range m { + err := g.Validator().Rules(`datetime`).CheckValue(ctx, k) + if v { + t.AssertNil(err) + } else { + t.AssertNE(err, nil) + } + } + }) +} + func Test_DateFormat(t *testing.T) { gtest.C(t, func(t *gtest.T) { val1 := "2010" diff --git a/util/gvalid/i18n/cn/validation.toml b/util/gvalid/i18n/cn/validation.toml index fb6c7c787..902fff880 100644 --- a/util/gvalid/i18n/cn/validation.toml +++ b/util/gvalid/i18n/cn/validation.toml @@ -5,7 +5,8 @@ "gf.gvalid.rule.required-with-all" = ":attribute 字段不能为空" "gf.gvalid.rule.required-without" = ":attribute 字段不能为空" "gf.gvalid.rule.required-without-all" = ":attribute 字段不能为空" -"gf.gvalid.rule.date" = ":attribute 日期格式不正确" +"gf.gvalid.rule.date" = ":attribute 日期格式不满足Y-m-d格式,例如: 2001-02-03" +"gf.gvalid.rule.datetime" = ":attribute 日期格式不满足Y-m-d H:i:s格式,例如: 2001-02-03 12:00:00" "gf.gvalid.rule.date-format" = ":attribute 日期格式不满足:format" "gf.gvalid.rule.email" = ":attribute 邮箱地址格式不正确" "gf.gvalid.rule.phone" = ":attribute 手机号码格式不正确" diff --git a/util/gvalid/i18n/en/validation.toml b/util/gvalid/i18n/en/validation.toml index 4de5880d2..4da8cca97 100644 --- a/util/gvalid/i18n/en/validation.toml +++ b/util/gvalid/i18n/en/validation.toml @@ -6,6 +6,7 @@ "gf.gvalid.rule.required-without" = "The :attribute field is required" "gf.gvalid.rule.required-without-all" = "The :attribute field is required" "gf.gvalid.rule.date" = "The :attribute value is not a valid date" +"gf.gvalid.rule.datetime" = "The :attribute value is not a valid datetime" "gf.gvalid.rule.date-format" = "The :attribute value does not match the format :format" "gf.gvalid.rule.email" = "The :attribute value must be a valid email address" "gf.gvalid.rule.phone" = "The :attribute value must be a valid phone number"