From fa241efa6d5e934f599e43714e970fa48c9a0f47 Mon Sep 17 00:00:00 2001 From: Unknwon Date: Wed, 15 Oct 2014 11:19:20 -0400 Subject: [PATCH] Use binding middleware --- .gopmfile | 1 + cmd/web.go | 2 +- modules/auth/admin.go | 7 +- modules/auth/apiv1/miscellaneous.go | 42 +-- modules/auth/auth.go | 42 +-- modules/auth/auth_form.go | 8 +- modules/auth/org.go | 16 +- modules/auth/publickey_form.go | 8 +- modules/auth/repo_form.go | 44 ++- modules/auth/user_form.go | 24 +- modules/middleware/binding/binding.go | 472 -------------------------- 11 files changed, 89 insertions(+), 577 deletions(-) delete mode 100644 modules/middleware/binding/binding.go diff --git a/.gopmfile b/.gopmfile index aca9e0feed59..e1ffb0154e9e 100644 --- a/.gopmfile +++ b/.gopmfile @@ -16,6 +16,7 @@ github.com/go-xorm/xorm = commit:2d8b3135b1 github.com/gogits/gfm = commit:40f747a9c0 github.com/gogits/oauth2 = commit:99cbec870a github.com/lib/pq = commit:b021d0ef20 +github.com/macaron-contrib/binding = github.com/macaron-contrib/cache = github.com/macaron-contrib/captcha = commit:d37d37eeea github.com/macaron-contrib/csrf = diff --git a/cmd/web.go b/cmd/web.go index f5843f5f393b..98a180a935c7 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -15,6 +15,7 @@ import ( "github.com/Unknwon/macaron" "github.com/codegangsta/cli" + "github.com/macaron-contrib/binding" "github.com/macaron-contrib/cache" "github.com/macaron-contrib/captcha" "github.com/macaron-contrib/csrf" @@ -30,7 +31,6 @@ import ( "github.com/gogits/gogs/modules/git" "github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/middleware" - "github.com/gogits/gogs/modules/middleware/binding" "github.com/gogits/gogs/modules/setting" "github.com/gogits/gogs/routers" "github.com/gogits/gogs/routers/admin" diff --git a/modules/auth/admin.go b/modules/auth/admin.go index 1f1260d65a3a..60ea3a3cc2c9 100644 --- a/modules/auth/admin.go +++ b/modules/auth/admin.go @@ -6,9 +6,8 @@ package auth import ( "github.com/Unknwon/macaron" - "github.com/macaron-contrib/i18n" - "github.com/gogits/gogs/modules/middleware/binding" + "github.com/macaron-contrib/binding" ) type AdminEditUserForm struct { @@ -22,6 +21,6 @@ type AdminEditUserForm struct { LoginType int `form:"login_type"` } -func (f *AdminEditUserForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { - validate(errs, ctx.Data, f, l) +func (f *AdminEditUserForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) } diff --git a/modules/auth/apiv1/miscellaneous.go b/modules/auth/apiv1/miscellaneous.go index ea3892977a9d..71f9541a9c2d 100644 --- a/modules/auth/apiv1/miscellaneous.go +++ b/modules/auth/apiv1/miscellaneous.go @@ -8,11 +8,9 @@ import ( "reflect" "github.com/Unknwon/macaron" - "github.com/macaron-contrib/i18n" + "github.com/macaron-contrib/binding" "github.com/gogits/gogs/modules/auth" - "github.com/gogits/gogs/modules/log" - "github.com/gogits/gogs/modules/middleware/binding" ) type MarkdownForm struct { @@ -21,18 +19,13 @@ type MarkdownForm struct { Context string `form:"context"` } -func (f *MarkdownForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { - validateApiReq(errs, ctx.Data, f, l) +func (f *MarkdownForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validateApiReq(errs, ctx.Data, f) } -func validateApiReq(errs *binding.Errors, data map[string]interface{}, f interface{}, l i18n.Locale) { - if errs.Count() == 0 { - return - } else if len(errs.Overall) > 0 { - for _, err := range errs.Overall { - log.Error(4, "%s: %v", reflect.TypeOf(f), err) - } - return +func validateApiReq(errs binding.Errors, data map[string]interface{}, f auth.Form) binding.Errors { + if errs.Len() == 0 { + return errs } data["HasError"] = true @@ -54,26 +47,27 @@ func validateApiReq(errs *binding.Errors, data map[string]interface{}, f interfa continue } - if err, ok := errs.Fields[field.Name]; ok { - switch err { - case binding.BindingRequireError: + if errs[0].FieldNames[0] == field.Name { + switch errs[0].Classification { + case binding.RequiredError: data["ErrorMsg"] = fieldName + " cannot be empty" - case binding.BindingAlphaDashError: + case binding.AlphaDashError: data["ErrorMsg"] = fieldName + " must be valid alpha or numeric or dash(-_) characters" - case binding.BindingAlphaDashDotError: + case binding.AlphaDashDotError: data["ErrorMsg"] = fieldName + " must be valid alpha or numeric or dash(-_) or dot characters" - case binding.BindingMinSizeError: + case binding.MinSizeError: data["ErrorMsg"] = fieldName + " must contain at least " + auth.GetMinSize(field) + " characters" - case binding.BindingMaxSizeError: + case binding.MaxSizeError: data["ErrorMsg"] = fieldName + " must contain at most " + auth.GetMaxSize(field) + " characters" - case binding.BindingEmailError: + case binding.EmailError: data["ErrorMsg"] = fieldName + " is not a valid e-mail address" - case binding.BindingUrlError: + case binding.UrlError: data["ErrorMsg"] = fieldName + " is not a valid URL" default: - data["ErrorMsg"] = "Unknown error: " + err + data["ErrorMsg"] = "Unknown error: " + errs[0].Classification } - return + return errs } } + return errs } diff --git a/modules/auth/auth.go b/modules/auth/auth.go index bcd2fcb35dad..95e647f53a30 100644 --- a/modules/auth/auth.go +++ b/modules/auth/auth.go @@ -9,12 +9,12 @@ import ( "reflect" "strings" - "github.com/macaron-contrib/i18n" + "github.com/Unknwon/macaron" + "github.com/macaron-contrib/binding" "github.com/macaron-contrib/session" "github.com/gogits/gogs/models" "github.com/gogits/gogs/modules/log" - "github.com/gogits/gogs/modules/middleware/binding" "github.com/gogits/gogs/modules/setting" ) @@ -69,6 +69,10 @@ func SignedInUser(header http.Header, sess session.Store) *models.User { return u } +type Form interface { + binding.Validator +} + // AssignForm assign form values back to the template data. func AssignForm(form interface{}, data map[string]interface{}) { typ := reflect.TypeOf(form) @@ -109,14 +113,9 @@ func GetMaxSize(field reflect.StructField) string { return getSize(field, "MaxSize(") } -func validate(errs *binding.Errors, data map[string]interface{}, f interface{}, l i18n.Locale) { - if errs.Count() == 0 { - return - } else if len(errs.Overall) > 0 { - for _, err := range errs.Overall { - log.Error(4, "%s: %v", reflect.TypeOf(f), err) - } - return +func validate(errs binding.Errors, data map[string]interface{}, f Form, l macaron.Locale) binding.Errors { + if errs.Len() == 0 { + return errs } data["HasError"] = true @@ -139,28 +138,29 @@ func validate(errs *binding.Errors, data map[string]interface{}, f interface{}, continue } - if err, ok := errs.Fields[field.Name]; ok { + if errs[0].FieldNames[0] == field.Name { data["Err_"+field.Name] = true trName := l.Tr("form." + field.Name) - switch err { - case binding.BindingRequireError: + switch errs[0].Classification { + case binding.RequiredError: data["ErrorMsg"] = trName + l.Tr("form.require_error") - case binding.BindingAlphaDashError: + case binding.AlphaDashError: data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_error") - case binding.BindingAlphaDashDotError: + case binding.AlphaDashDotError: data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_dot_error") - case binding.BindingMinSizeError: + case binding.MinSizeError: data["ErrorMsg"] = trName + l.Tr("form.min_size_error", GetMinSize(field)) - case binding.BindingMaxSizeError: + case binding.MaxSizeError: data["ErrorMsg"] = trName + l.Tr("form.max_size_error", GetMaxSize(field)) - case binding.BindingEmailError: + case binding.EmailError: data["ErrorMsg"] = trName + l.Tr("form.email_error") - case binding.BindingUrlError: + case binding.UrlError: data["ErrorMsg"] = trName + l.Tr("form.url_error") default: - data["ErrorMsg"] = l.Tr("form.unknown_error") + " " + err + data["ErrorMsg"] = l.Tr("form.unknown_error") + " " + errs[0].Classification } - return + return errs } } + return errs } diff --git a/modules/auth/auth_form.go b/modules/auth/auth_form.go index cb9da5dff488..e9789634c948 100644 --- a/modules/auth/auth_form.go +++ b/modules/auth/auth_form.go @@ -6,9 +6,7 @@ package auth import ( "github.com/Unknwon/macaron" - "github.com/macaron-contrib/i18n" - - "github.com/gogits/gogs/modules/middleware/binding" + "github.com/macaron-contrib/binding" ) type AuthenticationForm struct { @@ -31,6 +29,6 @@ type AuthenticationForm struct { AllowAutoRegister bool `form:"allowautoregister"` } -func (f *AuthenticationForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { - validate(errs, ctx.Data, f, l) +func (f *AuthenticationForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) } diff --git a/modules/auth/org.go b/modules/auth/org.go index 6183e8c82669..3e6c55c1829d 100644 --- a/modules/auth/org.go +++ b/modules/auth/org.go @@ -6,9 +6,7 @@ package auth import ( "github.com/Unknwon/macaron" - "github.com/macaron-contrib/i18n" - - "github.com/gogits/gogs/modules/middleware/binding" + "github.com/macaron-contrib/binding" ) // ________ .__ __ .__ @@ -23,8 +21,8 @@ type CreateOrgForm struct { Email string `form:"email" binding:"Required;Email;MaxSize(50)"` } -func (f *CreateOrgForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { - validate(errs, ctx.Data, f, l) +func (f *CreateOrgForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) } type UpdateOrgSettingForm struct { @@ -37,8 +35,8 @@ type UpdateOrgSettingForm struct { Avatar string `form:"avatar" binding:"Required;Email;MaxSize(50)"` } -func (f *UpdateOrgSettingForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { - validate(errs, ctx.Data, f, l) +func (f *UpdateOrgSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) } // ___________ @@ -54,6 +52,6 @@ type CreateTeamForm struct { Permission string `form:"permission"` } -func (f *CreateTeamForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { - validate(errs, ctx.Data, f, l) +func (f *CreateTeamForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) } diff --git a/modules/auth/publickey_form.go b/modules/auth/publickey_form.go index 4618a7ea3633..5a1d44c04b46 100644 --- a/modules/auth/publickey_form.go +++ b/modules/auth/publickey_form.go @@ -6,9 +6,7 @@ package auth import ( "github.com/Unknwon/macaron" - "github.com/macaron-contrib/i18n" - - "github.com/gogits/gogs/modules/middleware/binding" + "github.com/macaron-contrib/binding" ) type AddSSHKeyForm struct { @@ -16,6 +14,6 @@ type AddSSHKeyForm struct { Content string `form:"content" binding:"Required"` } -func (f *AddSSHKeyForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { - validate(errs, ctx.Data, f, l) +func (f *AddSSHKeyForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) } diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index df5b8b69fb5a..86fbdc4926fe 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -6,9 +6,7 @@ package auth import ( "github.com/Unknwon/macaron" - "github.com/macaron-contrib/i18n" - - "github.com/gogits/gogs/modules/middleware/binding" + "github.com/macaron-contrib/binding" ) // _______________________________________ _________.______________________ _______________.___. @@ -28,8 +26,8 @@ type CreateRepoForm struct { InitReadme bool `form:"init_readme"` } -func (f *CreateRepoForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { - validate(errs, ctx.Data, f, l) +func (f *CreateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) } type MigrateRepoForm struct { @@ -43,8 +41,8 @@ type MigrateRepoForm struct { Description string `form:"desc" binding:"MaxSize(255)"` } -func (f *MigrateRepoForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { - validate(errs, ctx.Data, f, l) +func (f *MigrateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) } type RepoSettingForm struct { @@ -57,8 +55,8 @@ type RepoSettingForm struct { GoGet bool `form:"goget"` } -func (f *RepoSettingForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { - validate(errs, ctx.Data, f, l) +func (f *RepoSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) } // __ __ ___. .__ .__ __ @@ -77,8 +75,8 @@ type NewWebhookForm struct { Active bool `form:"active"` } -func (f *NewWebhookForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { - validate(errs, ctx.Data, f, l) +func (f *NewWebhookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) } type NewSlackHookForm struct { @@ -90,8 +88,8 @@ type NewSlackHookForm struct { Active bool `form:"active"` } -func (f *NewSlackHookForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { - validate(errs, ctx.Data, f, l) +func (f *NewSlackHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) } // .___ @@ -109,8 +107,8 @@ type CreateIssueForm struct { Content string `form:"content"` } -func (f *CreateIssueForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { - validate(errs, ctx.Data, f, l) +func (f *CreateIssueForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) } // _____ .__.__ __ @@ -126,8 +124,8 @@ type CreateMilestoneForm struct { Deadline string `form:"due_date"` } -func (f *CreateMilestoneForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { - validate(errs, ctx.Data, f, l) +func (f *CreateMilestoneForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) } // .____ ___. .__ @@ -142,8 +140,8 @@ type CreateLabelForm struct { Color string `form:"color" binding:"Required;Size(7)"` } -func (f *CreateLabelForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { - validate(errs, ctx.Data, f, l) +func (f *CreateLabelForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) } // __________ .__ @@ -162,8 +160,8 @@ type NewReleaseForm struct { Prerelease bool `form:"prerelease"` } -func (f *NewReleaseForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { - validate(errs, ctx.Data, f, l) +func (f *NewReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) } type EditReleaseForm struct { @@ -174,6 +172,6 @@ type EditReleaseForm struct { Prerelease bool `form:"prerelease"` } -func (f *EditReleaseForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { - validate(errs, ctx.Data, f, l) +func (f *EditReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) } diff --git a/modules/auth/user_form.go b/modules/auth/user_form.go index 93bd01a9fd9a..72bdd4589f34 100644 --- a/modules/auth/user_form.go +++ b/modules/auth/user_form.go @@ -6,9 +6,7 @@ package auth import ( "github.com/Unknwon/macaron" - "github.com/macaron-contrib/i18n" - - "github.com/gogits/gogs/modules/middleware/binding" + "github.com/macaron-contrib/binding" ) type InstallForm struct { @@ -34,8 +32,8 @@ type InstallForm struct { AdminEmail string `form:"admin_email" binding:"Required;Email;MaxSize(50)"` } -func (f *InstallForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { - validate(errs, ctx.Data, f, l) +func (f *InstallForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) } // _____ ____ _________________ ___ @@ -54,8 +52,8 @@ type RegisterForm struct { LoginName string `form:"loginname"` } -func (f *RegisterForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { - validate(errs, ctx.Data, f, l) +func (f *RegisterForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) } type SignInForm struct { @@ -64,8 +62,8 @@ type SignInForm struct { Remember bool `form:"remember"` } -func (f *SignInForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { - validate(errs, ctx.Data, f, l) +func (f *SignInForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) } // __________________________________________.___ _______ ________ _________ @@ -84,8 +82,8 @@ type UpdateProfileForm struct { Avatar string `form:"avatar" binding:"Required;Email;MaxSize(50)"` } -func (f *UpdateProfileForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { - validate(errs, ctx.Data, f, l) +func (f *UpdateProfileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) } type ChangePasswordForm struct { @@ -94,6 +92,6 @@ type ChangePasswordForm struct { Retype string `form:"retype"` } -func (f *ChangePasswordForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { - validate(errs, ctx.Data, f, l) +func (f *ChangePasswordForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) } diff --git a/modules/middleware/binding/binding.go b/modules/middleware/binding/binding.go deleted file mode 100644 index 4103f5543fbe..000000000000 --- a/modules/middleware/binding/binding.go +++ /dev/null @@ -1,472 +0,0 @@ -// Copyright 2013 The Martini Contrib Authors. All rights reserved. -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package binding - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "reflect" - "regexp" - "strconv" - "strings" - "unicode/utf8" - - "github.com/Unknwon/macaron" - "github.com/macaron-contrib/i18n" -) - -/* - To the land of Middle-ware Earth: - - One func to rule them all, - One func to find them, - One func to bring them all, - And in this package BIND them. -*/ - -// Bind accepts a copy of an empty struct and populates it with -// values from the request (if deserialization is successful). It -// wraps up the functionality of the Form and Json middleware -// according to the Content-Type of the request, and it guesses -// if no Content-Type is specified. Bind invokes the ErrorHandler -// middleware to bail out if errors occurred. If you want to perform -// your own error handling, use Form or Json middleware directly. -// An interface pointer can be added as a second argument in order -// to map the struct to a specific interface. -func Bind(obj interface{}, ifacePtr ...interface{}) macaron.Handler { - return func(ctx *macaron.Context) { - contentType := ctx.Req.Header.Get("Content-Type") - - if strings.Contains(contentType, "form-urlencoded") { - ctx.Invoke(Form(obj, ifacePtr...)) - } else if strings.Contains(contentType, "multipart/form-data") { - ctx.Invoke(MultipartForm(obj, ifacePtr...)) - } else if strings.Contains(contentType, "json") { - ctx.Invoke(Json(obj, ifacePtr...)) - } else { - ctx.Invoke(Json(obj, ifacePtr...)) - if getErrors(ctx).Count() > 0 { - ctx.Invoke(Form(obj, ifacePtr...)) - } - } - - ctx.Invoke(ErrorHandler) - } -} - -// BindIgnErr will do the exactly same thing as Bind but without any -// error handling, which user has freedom to deal with them. -// This allows user take advantages of validation. -func BindIgnErr(obj interface{}, ifacePtr ...interface{}) macaron.Handler { - return func(ctx *macaron.Context, req *http.Request) { - contentType := req.Header.Get("Content-Type") - - if strings.Contains(contentType, "form-urlencoded") { - ctx.Invoke(Form(obj, ifacePtr...)) - } else if strings.Contains(contentType, "multipart/form-data") { - ctx.Invoke(MultipartForm(obj, ifacePtr...)) - } else if strings.Contains(contentType, "json") { - ctx.Invoke(Json(obj, ifacePtr...)) - } else { - ctx.Invoke(Json(obj, ifacePtr...)) - if getErrors(ctx).Count() > 0 { - ctx.Invoke(Form(obj, ifacePtr...)) - } - } - } -} - -// Form is middleware to deserialize form-urlencoded data from the request. -// It gets data from the form-urlencoded body, if present, or from the -// query string. It uses the http.Request.ParseForm() method -// to perform deserialization, then reflection is used to map each field -// into the struct with the proper type. Structs with primitive slice types -// (bool, float, int, string) can support deserialization of repeated form -// keys, for example: key=val1&key=val2&key=val3 -// An interface pointer can be added as a second argument in order -// to map the struct to a specific interface. -func Form(formStruct interface{}, ifacePtr ...interface{}) macaron.Handler { - return func(ctx *macaron.Context) { - ensureNotPointer(formStruct) - formStruct := reflect.New(reflect.TypeOf(formStruct)) - errors := newErrors() - parseErr := ctx.Req.ParseForm() - - // Format validation of the request body or the URL would add considerable overhead, - // and ParseForm does not complain when URL encoding is off. - // Because an empty request body or url can also mean absence of all needed values, - // it is not in all cases a bad request, so let's return 422. - if parseErr != nil { - errors.Overall[BindingDeserializationError] = parseErr.Error() - } - - mapForm(formStruct, ctx.Req.Form, errors) - - validateAndMap(formStruct, ctx, errors, ifacePtr...) - } -} - -func MultipartForm(formStruct interface{}, ifacePtr ...interface{}) macaron.Handler { - return func(ctx *macaron.Context) { - ensureNotPointer(formStruct) - formStruct := reflect.New(reflect.TypeOf(formStruct)) - errors := newErrors() - - // Workaround for multipart forms returning nil instead of an error - // when content is not multipart - // https://code.google.com/p/go/issues/detail?id=6334 - multipartReader, err := ctx.Req.MultipartReader() - if err != nil { - errors.Overall[BindingDeserializationError] = err.Error() - } else { - form, parseErr := multipartReader.ReadForm(MaxMemory) - - if parseErr != nil { - errors.Overall[BindingDeserializationError] = parseErr.Error() - } - - ctx.Req.MultipartForm = form - } - - mapForm(formStruct, ctx.Req.MultipartForm.Value, errors) - - validateAndMap(formStruct, ctx, errors, ifacePtr...) - } -} - -// Json is middleware to deserialize a JSON payload from the request -// into the struct that is passed in. The resulting struct is then -// validated, but no error handling is actually performed here. -// An interface pointer can be added as a second argument in order -// to map the struct to a specific interface. -func Json(jsonStruct interface{}, ifacePtr ...interface{}) macaron.Handler { - return func(ctx *macaron.Context) { - ensureNotPointer(jsonStruct) - jsonStruct := reflect.New(reflect.TypeOf(jsonStruct)) - errors := newErrors() - - if ctx.Req.Body != nil { - defer ctx.Req.Body.Close() - } - - if err := json.NewDecoder(ctx.Req.Body).Decode(jsonStruct.Interface()); err != nil && err != io.EOF { - errors.Overall[BindingDeserializationError] = err.Error() - } - - validateAndMap(jsonStruct, ctx, errors, ifacePtr...) - } -} - -// Validate is middleware to enforce required fields. If the struct -// passed in is a Validator, then the user-defined Validate method -// is executed, and its errors are mapped to the context. This middleware -// performs no error handling: it merely detects them and maps them. -func Validate(obj interface{}) macaron.Handler { - return func(ctx *macaron.Context, l i18n.Locale) { - errors := newErrors() - validateStruct(errors, obj) - - if validator, ok := obj.(Validator); ok { - validator.Validate(ctx, errors, l) - } - ctx.Map(*errors) - } -} - -var ( - alphaDashPattern = regexp.MustCompile("[^\\d\\w-_]") - alphaDashDotPattern = regexp.MustCompile("[^\\d\\w-_\\.]") - emailPattern = regexp.MustCompile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?") - urlPattern = regexp.MustCompile(`(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?`) -) - -func validateStruct(errors *Errors, obj interface{}) { - typ := reflect.TypeOf(obj) - val := reflect.ValueOf(obj) - - if typ.Kind() == reflect.Ptr { - typ = typ.Elem() - val = val.Elem() - } - - for i := 0; i < typ.NumField(); i++ { - field := typ.Field(i) - - // Allow ignored fields in the struct - if field.Tag.Get("form") == "-" { - continue - } - - fieldValue := val.Field(i).Interface() - if field.Type.Kind() == reflect.Struct { - validateStruct(errors, fieldValue) - continue - } - - zero := reflect.Zero(field.Type).Interface() - - // Match rules. - for _, rule := range strings.Split(field.Tag.Get("binding"), ";") { - if len(rule) == 0 { - continue - } - - switch { - case rule == "Required": - if reflect.DeepEqual(zero, fieldValue) { - errors.Fields[field.Name] = BindingRequireError - break - } - case rule == "AlphaDash": - if alphaDashPattern.MatchString(fmt.Sprintf("%v", fieldValue)) { - errors.Fields[field.Name] = BindingAlphaDashError - break - } - case rule == "AlphaDashDot": - if alphaDashDotPattern.MatchString(fmt.Sprintf("%v", fieldValue)) { - errors.Fields[field.Name] = BindingAlphaDashDotError - break - } - case strings.HasPrefix(rule, "MinSize("): - min, err := strconv.Atoi(rule[8 : len(rule)-1]) - if err != nil { - errors.Overall["MinSize"] = err.Error() - break - } - if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) < min { - errors.Fields[field.Name] = BindingMinSizeError - break - } - v := reflect.ValueOf(fieldValue) - if v.Kind() == reflect.Slice && v.Len() < min { - errors.Fields[field.Name] = BindingMinSizeError - break - } - case strings.HasPrefix(rule, "MaxSize("): - max, err := strconv.Atoi(rule[8 : len(rule)-1]) - if err != nil { - errors.Overall["MaxSize"] = err.Error() - break - } - if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) > max { - errors.Fields[field.Name] = BindingMaxSizeError - break - } - v := reflect.ValueOf(fieldValue) - if v.Kind() == reflect.Slice && v.Len() > max { - errors.Fields[field.Name] = BindingMinSizeError - break - } - case rule == "Email": - if !emailPattern.MatchString(fmt.Sprintf("%v", fieldValue)) { - errors.Fields[field.Name] = BindingEmailError - break - } - case rule == "Url": - str := fmt.Sprintf("%v", fieldValue) - if len(str) == 0 { - continue - } else if !urlPattern.MatchString(str) { - errors.Fields[field.Name] = BindingUrlError - break - } - } - } - } -} - -func mapForm(formStruct reflect.Value, form map[string][]string, errors *Errors) { - typ := formStruct.Elem().Type() - - for i := 0; i < typ.NumField(); i++ { - typeField := typ.Field(i) - if inputFieldName := typeField.Tag.Get("form"); inputFieldName != "" { - structField := formStruct.Elem().Field(i) - if !structField.CanSet() { - continue - } - - inputValue, exists := form[inputFieldName] - - if !exists { - continue - } - - numElems := len(inputValue) - if structField.Kind() == reflect.Slice && numElems > 0 { - sliceOf := structField.Type().Elem().Kind() - slice := reflect.MakeSlice(structField.Type(), numElems, numElems) - for i := 0; i < numElems; i++ { - setWithProperType(sliceOf, inputValue[i], slice.Index(i), inputFieldName, errors) - } - formStruct.Elem().Field(i).Set(slice) - } else { - setWithProperType(typeField.Type.Kind(), inputValue[0], structField, inputFieldName, errors) - } - } - } -} - -// ErrorHandler simply counts the number of errors in the -// context and, if more than 0, writes a 400 Bad Request -// response and a JSON payload describing the errors with -// the "Content-Type" set to "application/json". -// Middleware remaining on the stack will not even see the request -// if, by this point, there are any errors. -// This is a "default" handler, of sorts, and you are -// welcome to use your own instead. The Bind middleware -// invokes this automatically for convenience. -func ErrorHandler(errs Errors, resp http.ResponseWriter) { - if errs.Count() > 0 { - resp.Header().Set("Content-Type", "application/json; charset=utf-8") - if _, ok := errs.Overall[BindingDeserializationError]; ok { - resp.WriteHeader(http.StatusBadRequest) - } else { - resp.WriteHeader(422) - } - errOutput, _ := json.Marshal(errs) - resp.Write(errOutput) - return - } -} - -// This sets the value in a struct of an indeterminate type to the -// matching value from the request (via Form middleware) in the -// same type, so that not all deserialized values have to be strings. -// Supported types are string, int, float, and bool. -func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value, nameInTag string, errors *Errors) { - switch valueKind { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - if val == "" { - val = "0" - } - intVal, err := strconv.ParseInt(val, 10, 64) - if err != nil { - errors.Fields[nameInTag] = BindingIntegerTypeError - } else { - structField.SetInt(intVal) - } - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - if val == "" { - val = "0" - } - uintVal, err := strconv.ParseUint(val, 10, 64) - if err != nil { - errors.Fields[nameInTag] = BindingIntegerTypeError - } else { - structField.SetUint(uintVal) - } - case reflect.Bool: - structField.SetBool(val == "on") - case reflect.Float32: - if val == "" { - val = "0.0" - } - floatVal, err := strconv.ParseFloat(val, 32) - if err != nil { - errors.Fields[nameInTag] = BindingFloatTypeError - } else { - structField.SetFloat(floatVal) - } - case reflect.Float64: - if val == "" { - val = "0.0" - } - floatVal, err := strconv.ParseFloat(val, 64) - if err != nil { - errors.Fields[nameInTag] = BindingFloatTypeError - } else { - structField.SetFloat(floatVal) - } - case reflect.String: - structField.SetString(val) - } -} - -// Don't pass in pointers to bind to. Can lead to bugs. -func ensureNotPointer(obj interface{}) { - if reflect.TypeOf(obj).Kind() == reflect.Ptr { - panic("Pointers are not accepted as binding models") - } -} - -// Performs validation and combines errors from validation -// with errors from deserialization, then maps both the -// resulting struct and the errors to the context. -func validateAndMap(obj reflect.Value, ctx *macaron.Context, errors *Errors, ifacePtr ...interface{}) { - ctx.Invoke(Validate(obj.Interface())) - errors.Combine(getErrors(ctx)) - ctx.Map(*errors) - ctx.Map(obj.Elem().Interface()) - if len(ifacePtr) > 0 { - ctx.MapTo(obj.Elem().Interface(), ifacePtr[0]) - } -} - -func newErrors() *Errors { - return &Errors{make(map[string]string), make(map[string]string)} -} - -func getErrors(ctx *macaron.Context) Errors { - return ctx.GetVal(reflect.TypeOf(Errors{})).Interface().(Errors) -} - -type ( - // Implement the Validator interface to define your own input - // validation before the request even gets to your application. - // The Validate method will be executed during the validation phase. - Validator interface { - Validate(*macaron.Context, *Errors, i18n.Locale) - } -) - -var ( - // Maximum amount of memory to use when parsing a multipart form. - // Set this to whatever value you prefer; default is 10 MB. - MaxMemory = int64(1024 * 1024 * 10) -) - -// Errors represents the contract of the response body when the -// binding step fails before getting to the application. -type Errors struct { - Overall map[string]string `json:"overall"` - Fields map[string]string `json:"fields"` -} - -// Total errors is the sum of errors with the request overall -// and errors on individual fields. -func (err Errors) Count() int { - return len(err.Overall) + len(err.Fields) -} - -func (this *Errors) Combine(other Errors) { - for key, val := range other.Fields { - if _, exists := this.Fields[key]; !exists { - this.Fields[key] = val - } - } - for key, val := range other.Overall { - if _, exists := this.Overall[key]; !exists { - this.Overall[key] = val - } - } -} - -const ( - BindingRequireError string = "Required" - BindingAlphaDashError string = "AlphaDash" - BindingAlphaDashDotError string = "AlphaDashDot" - BindingMinSizeError string = "MinSize" - BindingMaxSizeError string = "MaxSize" - BindingEmailError string = "Email" - BindingUrlError string = "Url" - BindingDeserializationError string = "DeserializationError" - BindingIntegerTypeError string = "IntegerTypeError" - BindingBooleanTypeError string = "BooleanTypeError" - BindingFloatTypeError string = "FloatTypeError" -)