|
2 | 2 | package {{packageName}}
|
3 | 3 |
|
4 | 4 | import (
|
| 5 | + "encoding/json" |
| 6 | + "errors" |
| 7 | + "io" |
| 8 | + "mime/multipart" |
| 9 | + "net/http" |
| 10 | + "net/url" |
| 11 | + "os" |
5 | 12 | "reflect"
|
| 13 | + "strconv" |
| 14 | + "strings" |
| 15 | + "time" |
6 | 16 | )
|
7 | 17 |
|
8 | 18 | // Response return a ImplResponse struct filled
|
@@ -64,3 +74,292 @@ func AssertRecurseValueRequired[T any](value reflect.Value, callback func(T) err
|
64 | 74 | }
|
65 | 75 | return nil
|
66 | 76 | }
|
| 77 | + |
| 78 | +// EncodeJSONResponse uses the json encoder to write an interface to the http response with an optional status code |
| 79 | +func EncodeJSONResponse(i interface{}, status *int,{{#addResponseHeaders}} headers map[string][]string,{{/addResponseHeaders}} w http.ResponseWriter) error { |
| 80 | + wHeader := w.Header() |
| 81 | + {{#addResponseHeaders}} |
| 82 | + for key, values := range headers { |
| 83 | + for _, value := range values { |
| 84 | + wHeader.Add(key, value) |
| 85 | + } |
| 86 | + } |
| 87 | + {{/addResponseHeaders}} |
| 88 | + |
| 89 | + f, ok := i.(*os.File) |
| 90 | + if ok { |
| 91 | + data, err := io.ReadAll(f) |
| 92 | + if err != nil { |
| 93 | + return err |
| 94 | + } |
| 95 | + wHeader.Set("Content-Type", http.DetectContentType(data)) |
| 96 | + wHeader.Set("Content-Disposition", "attachment; filename="+f.Name()) |
| 97 | + if status != nil { |
| 98 | + w.WriteHeader(*status) |
| 99 | + } else { |
| 100 | + w.WriteHeader(http.StatusOK) |
| 101 | + } |
| 102 | + _, err = w.Write(data) |
| 103 | + return err |
| 104 | + } |
| 105 | + wHeader.Set("Content-Type", "application/json; charset=UTF-8") |
| 106 | + |
| 107 | + if status != nil { |
| 108 | + w.WriteHeader(*status) |
| 109 | + } else { |
| 110 | + w.WriteHeader(http.StatusOK) |
| 111 | + } |
| 112 | + |
| 113 | + if i != nil { |
| 114 | + return json.NewEncoder(w).Encode(i) |
| 115 | + } |
| 116 | + |
| 117 | + return nil |
| 118 | +} |
| 119 | + |
| 120 | +// ReadFormFileToTempFile reads file data from a request form and writes it to a temporary file |
| 121 | +func ReadFormFileToTempFile(r *http.Request, key string) (*os.File, error) { |
| 122 | + _, fileHeader, err := r.FormFile(key) |
| 123 | + if err != nil { |
| 124 | + return nil, err |
| 125 | + } |
| 126 | + |
| 127 | + return readFileHeaderToTempFile(fileHeader) |
| 128 | +} |
| 129 | + |
| 130 | +// ReadFormFilesToTempFiles reads files array data from a request form and writes it to a temporary files |
| 131 | +func ReadFormFilesToTempFiles(r *http.Request, key string) ([]*os.File, error) { |
| 132 | + if err := r.ParseMultipartForm(32 << 20); err != nil { |
| 133 | + return nil, err |
| 134 | + } |
| 135 | + |
| 136 | + files := make([]*os.File, 0, len(r.MultipartForm.File[key])) |
| 137 | + |
| 138 | + for _, fileHeader := range r.MultipartForm.File[key] { |
| 139 | + file, err := readFileHeaderToTempFile(fileHeader) |
| 140 | + if err != nil { |
| 141 | + return nil, err |
| 142 | + } |
| 143 | + |
| 144 | + files = append(files, file) |
| 145 | + } |
| 146 | + |
| 147 | + return files, nil |
| 148 | +} |
| 149 | + |
| 150 | +// readFileHeaderToTempFile reads multipart.FileHeader and writes it to a temporary file |
| 151 | +func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error) { |
| 152 | + formFile, err := fileHeader.Open() |
| 153 | + if err != nil { |
| 154 | + return nil, err |
| 155 | + } |
| 156 | + |
| 157 | + defer formFile.Close() |
| 158 | + |
| 159 | + // Use .* as suffix, because the asterisk is a placeholder for the random value, |
| 160 | + // and the period allows consumers of this file to remove the suffix to obtain the original file name |
| 161 | + file, err := os.CreateTemp("", fileHeader.Filename+".*") |
| 162 | + if err != nil { |
| 163 | + return nil, err |
| 164 | + } |
| 165 | + |
| 166 | + defer file.Close() |
| 167 | + |
| 168 | + _, err = io.Copy(file, formFile) |
| 169 | + if err != nil { |
| 170 | + return nil, err |
| 171 | + } |
| 172 | + |
| 173 | + return file, nil |
| 174 | +} |
| 175 | + |
| 176 | +func parseTimes(param string) ([]time.Time, error) { |
| 177 | + splits := strings.Split(param, ",") |
| 178 | + times := make([]time.Time, 0, len(splits)) |
| 179 | + for _, v := range splits { |
| 180 | + t, err := parseTime(v) |
| 181 | + if err != nil { |
| 182 | + return nil, err |
| 183 | + } |
| 184 | + times = append(times, t) |
| 185 | + } |
| 186 | + return times, nil |
| 187 | +} |
| 188 | + |
| 189 | +// parseTime will parses a string parameter into a time.Time using the RFC3339 format |
| 190 | +func parseTime(param string) (time.Time, error) { |
| 191 | + if param == "" { |
| 192 | + return time.Time{}, nil |
| 193 | + } |
| 194 | + return time.Parse(time.RFC3339, param) |
| 195 | +} |
| 196 | + |
| 197 | +type Number interface { |
| 198 | + ~int32 | ~int64 | ~float32 | ~float64 |
| 199 | +} |
| 200 | + |
| 201 | +type ParseString[T Number | string | bool] func(v string) (T, error) |
| 202 | + |
| 203 | +// parseFloat64 parses a string parameter to an float64. |
| 204 | +func parseFloat64(param string) (float64, error) { |
| 205 | + if param == "" { |
| 206 | + return 0, nil |
| 207 | + } |
| 208 | + |
| 209 | + return strconv.ParseFloat(param, 64) |
| 210 | +} |
| 211 | + |
| 212 | +// parseFloat32 parses a string parameter to an float32. |
| 213 | +func parseFloat32(param string) (float32, error) { |
| 214 | + if param == "" { |
| 215 | + return 0, nil |
| 216 | + } |
| 217 | + |
| 218 | + v, err := strconv.ParseFloat(param, 32) |
| 219 | + return float32(v), err |
| 220 | +} |
| 221 | + |
| 222 | +// parseInt64 parses a string parameter to an int64. |
| 223 | +func parseInt64(param string) (int64, error) { |
| 224 | + if param == "" { |
| 225 | + return 0, nil |
| 226 | + } |
| 227 | + |
| 228 | + return strconv.ParseInt(param, 10, 64) |
| 229 | +} |
| 230 | + |
| 231 | +// parseInt32 parses a string parameter to an int32. |
| 232 | +func parseInt32(param string) (int32, error) { |
| 233 | + if param == "" { |
| 234 | + return 0, nil |
| 235 | + } |
| 236 | + |
| 237 | + val, err := strconv.ParseInt(param, 10, 32) |
| 238 | + return int32(val), err |
| 239 | +} |
| 240 | + |
| 241 | +// parseBool parses a string parameter to an bool. |
| 242 | +func parseBool(param string) (bool, error) { |
| 243 | + if param == "" { |
| 244 | + return false, nil |
| 245 | + } |
| 246 | + |
| 247 | + return strconv.ParseBool(param) |
| 248 | +} |
| 249 | + |
| 250 | +type Operation[T Number | string | bool] func(actual string) (T, bool, error) |
| 251 | + |
| 252 | +func WithRequire[T Number | string | bool](parse ParseString[T]) Operation[T] { |
| 253 | + var empty T |
| 254 | + return func(actual string) (T, bool, error) { |
| 255 | + if actual == "" { |
| 256 | + return empty, false, errors.New(errMsgRequiredMissing) |
| 257 | + } |
| 258 | + |
| 259 | + v, err := parse(actual) |
| 260 | + return v, false, err |
| 261 | + } |
| 262 | +} |
| 263 | + |
| 264 | +func WithDefaultOrParse[T Number | string | bool](def T, parse ParseString[T]) Operation[T] { |
| 265 | + return func(actual string) (T, bool, error) { |
| 266 | + if actual == "" { |
| 267 | + return def, true, nil |
| 268 | + } |
| 269 | + |
| 270 | + v, err := parse(actual) |
| 271 | + return v, false, err |
| 272 | + } |
| 273 | +} |
| 274 | + |
| 275 | +func WithParse[T Number | string | bool](parse ParseString[T]) Operation[T] { |
| 276 | + return func(actual string) (T, bool, error) { |
| 277 | + v, err := parse(actual) |
| 278 | + return v, false, err |
| 279 | + } |
| 280 | +} |
| 281 | + |
| 282 | +type Constraint[T Number | string | bool] func(actual T) error |
| 283 | + |
| 284 | +func WithMinimum[T Number](expected T) Constraint[T] { |
| 285 | + return func(actual T) error { |
| 286 | + if actual < expected { |
| 287 | + return errors.New(errMsgMinValueConstraint) |
| 288 | + } |
| 289 | + |
| 290 | + return nil |
| 291 | + } |
| 292 | +} |
| 293 | + |
| 294 | +func WithMaximum[T Number](expected T) Constraint[T] { |
| 295 | + return func(actual T) error { |
| 296 | + if actual > expected { |
| 297 | + return errors.New(errMsgMaxValueConstraint) |
| 298 | + } |
| 299 | + |
| 300 | + return nil |
| 301 | + } |
| 302 | +} |
| 303 | + |
| 304 | +// parseNumericParameter parses a numeric parameter to its respective type. |
| 305 | +func parseNumericParameter[T Number](param string, fn Operation[T], checks ...Constraint[T]) (T, error) { |
| 306 | + v, ok, err := fn(param) |
| 307 | + if err != nil { |
| 308 | + return 0, err |
| 309 | + } |
| 310 | + |
| 311 | + if !ok { |
| 312 | + for _, check := range checks { |
| 313 | + if err := check(v); err != nil { |
| 314 | + return 0, err |
| 315 | + } |
| 316 | + } |
| 317 | + } |
| 318 | + |
| 319 | + return v, nil |
| 320 | +} |
| 321 | + |
| 322 | +// parseBoolParameter parses a string parameter to a bool |
| 323 | +func parseBoolParameter(param string, fn Operation[bool]) (bool, error) { |
| 324 | + v, _, err := fn(param) |
| 325 | + return v, err |
| 326 | +} |
| 327 | + |
| 328 | +// parseNumericArrayParameter parses a string parameter containing array of values to its respective type. |
| 329 | +func parseNumericArrayParameter[T Number](param, delim string, required bool, fn Operation[T], checks ...Constraint[T]) ([]T, error) { |
| 330 | + if param == "" { |
| 331 | + if required { |
| 332 | + return nil, errors.New(errMsgRequiredMissing) |
| 333 | + } |
| 334 | + |
| 335 | + return nil, nil |
| 336 | + } |
| 337 | + |
| 338 | + str := strings.Split(param, delim) |
| 339 | + values := make([]T, len(str)) |
| 340 | + |
| 341 | + for i, s := range str { |
| 342 | + v, ok, err := fn(s) |
| 343 | + if err != nil { |
| 344 | + return nil, err |
| 345 | + } |
| 346 | + |
| 347 | + if !ok { |
| 348 | + for _, check := range checks { |
| 349 | + if err := check(v); err != nil { |
| 350 | + return nil, err |
| 351 | + } |
| 352 | + } |
| 353 | + } |
| 354 | + |
| 355 | + values[i] = v |
| 356 | + } |
| 357 | + |
| 358 | + return values, nil |
| 359 | +} |
| 360 | + |
| 361 | + |
| 362 | +// parseQuery parses query parameters and returns an error if any malformed value pairs are encountered. |
| 363 | +func parseQuery(rawQuery string) (url.Values, error) { |
| 364 | + return url.ParseQuery(rawQuery) |
| 365 | +} |
0 commit comments