loading up the forgejo repo on tangled to test page performance
1// Copyright 2014 The Gogs Authors. All rights reserved.
2// Copyright 2019 The Gitea Authors. All rights reserved.
3// SPDX-License-Identifier: MIT
4
5package middleware
6
7import (
8 "reflect"
9 "strings"
10
11 "forgejo.org/modules/setting"
12 "forgejo.org/modules/translation"
13 "forgejo.org/modules/util"
14 "forgejo.org/modules/validation"
15
16 "code.forgejo.org/go-chi/binding"
17)
18
19// Form form binding interface
20type Form interface {
21 binding.Validator
22}
23
24func init() {
25 binding.SetNameMapper(util.ToSnakeCase)
26}
27
28// AssignForm assign form values back to the template data.
29func AssignForm(form any, data map[string]any) {
30 typ := reflect.TypeOf(form)
31 val := reflect.ValueOf(form)
32
33 for typ.Kind() == reflect.Ptr {
34 typ = typ.Elem()
35 val = val.Elem()
36 }
37
38 for i := 0; i < typ.NumField(); i++ {
39 field := typ.Field(i)
40
41 fieldName := field.Tag.Get("form")
42 // Allow ignored fields in the struct
43 if fieldName == "-" {
44 continue
45 } else if len(fieldName) == 0 {
46 fieldName = util.ToSnakeCase(field.Name)
47 }
48
49 data[fieldName] = val.Field(i).Interface()
50 }
51}
52
53func getRuleBody(field reflect.StructField, prefix string) string {
54 for _, rule := range strings.Split(field.Tag.Get("binding"), ";") {
55 if strings.HasPrefix(rule, prefix) {
56 return rule[len(prefix) : len(rule)-1]
57 }
58 }
59 return ""
60}
61
62// GetSize get size int form tag
63func GetSize(field reflect.StructField) string {
64 return getRuleBody(field, "Size(")
65}
66
67// GetMinSize get minimal size in form tag
68func GetMinSize(field reflect.StructField) string {
69 return getRuleBody(field, "MinSize(")
70}
71
72// GetMaxSize get max size in form tag
73func GetMaxSize(field reflect.StructField) string {
74 return getRuleBody(field, "MaxSize(")
75}
76
77// GetInclude get include in form tag
78func GetInclude(field reflect.StructField) string {
79 return getRuleBody(field, "Include(")
80}
81
82func GetRange(field reflect.StructField) (string, string) {
83 min, max, _ := strings.Cut(getRuleBody(field, "Range("), ",")
84 return min, max
85}
86
87// Validate populates the data with validation error (if any).
88func Validate(errs binding.Errors, data map[string]any, f any, l translation.Locale) binding.Errors {
89 if errs.Len() == 0 {
90 return errs
91 }
92
93 data["HasError"] = true
94 // If the field with name errs[0].FieldNames[0] is not found in form
95 // somehow, some code later on will panic on Data["ErrorMsg"].(string).
96 // So initialize it to some default.
97 data["ErrorMsg"] = l.Tr("form.unknown_error")
98 AssignForm(f, data)
99
100 typ := reflect.TypeOf(f)
101
102 if typ.Kind() == reflect.Ptr {
103 typ = typ.Elem()
104 }
105
106 if field, ok := typ.FieldByName(errs[0].FieldNames[0]); ok {
107 fieldName := field.Tag.Get("form")
108 if fieldName != "-" {
109 data["Err_"+field.Name] = true
110
111 trName := field.Tag.Get("locale")
112 if len(trName) == 0 {
113 trName = l.TrString("form." + field.Name)
114 } else {
115 trName = l.TrString(trName)
116 }
117
118 switch errs[0].Classification {
119 case binding.ERR_REQUIRED:
120 data["ErrorMsg"] = trName + l.TrString("form.require_error")
121 case binding.ERR_ALPHA_DASH:
122 data["ErrorMsg"] = trName + l.TrString("form.alpha_dash_error")
123 case binding.ERR_ALPHA_DASH_DOT:
124 data["ErrorMsg"] = trName + l.TrString("form.alpha_dash_dot_error")
125 case validation.ErrGitRefName:
126 data["ErrorMsg"] = trName + l.TrString("form.git_ref_name_error")
127 case binding.ERR_SIZE:
128 data["ErrorMsg"] = trName + l.TrString("form.size_error", GetSize(field))
129 case binding.ERR_MIN_SIZE:
130 data["ErrorMsg"] = trName + l.TrString("form.min_size_error", GetMinSize(field))
131 case binding.ERR_MAX_SIZE:
132 data["ErrorMsg"] = trName + l.TrString("form.max_size_error", GetMaxSize(field))
133 case binding.ERR_EMAIL:
134 data["ErrorMsg"] = trName + l.TrString("form.email_error")
135 case binding.ERR_URL:
136 data["ErrorMsg"] = trName + l.TrString("form.url_error", errs[0].Message)
137 case binding.ERR_INCLUDE:
138 data["ErrorMsg"] = trName + l.TrString("form.include_error", GetInclude(field))
139 case binding.ERR_RANGE:
140 min, max := GetRange(field)
141 data["ErrorMsg"] = trName + l.TrString("alert.range_error", l.PrettyNumber(min), l.PrettyNumber(max))
142 case validation.ErrGlobPattern:
143 data["ErrorMsg"] = trName + l.TrString("form.glob_pattern_error", errs[0].Message)
144 case validation.ErrRegexPattern:
145 data["ErrorMsg"] = trName + l.TrString("form.regex_pattern_error", errs[0].Message)
146 case validation.ErrUsername:
147 if setting.Service.AllowDotsInUsernames {
148 data["ErrorMsg"] = trName + l.TrString("form.username_error")
149 } else {
150 data["ErrorMsg"] = trName + l.TrString("form.username_error_no_dots")
151 }
152 case validation.ErrInvalidGroupTeamMap:
153 data["ErrorMsg"] = trName + l.TrString("form.invalid_group_team_map_error", errs[0].Message)
154 case validation.ErrEmail:
155 data["ErrorMsg"] = trName + l.TrString("form.email_error")
156 default:
157 msg := errs[0].Classification
158 if msg != "" && errs[0].Message != "" {
159 msg += ": "
160 }
161
162 msg += errs[0].Message
163 if msg == "" {
164 msg = l.TrString("form.unknown_error")
165 }
166 data["ErrorMsg"] = trName + ": " + msg
167 }
168 return errs
169 }
170 }
171 return errs
172}