Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,17 @@
package {{packageName}}

import (
"encoding/json"
"errors"
"io"
"mime/multipart"
"net/http"
"net/url"
"os"
"reflect"
"strconv"
"strings"
"time"
)

// Response return a ImplResponse struct filled
Expand Down Expand Up @@ -64,3 +74,291 @@ func AssertRecurseValueRequired[T any](value reflect.Value, callback func(T) err
}
return nil
}

// EncodeJSONResponse uses the json encoder to write an interface to the http response with an optional status code
func EncodeJSONResponse(i interface{}, status *int,{{#addResponseHeaders}} headers map[string][]string,{{/addResponseHeaders}} w http.ResponseWriter) error {
wHeader := w.Header()
{{#addResponseHeaders}}
for key, values := range headers {
for _, value := range values {
wHeader.Add(key, value)
}
}
{{/addResponseHeaders}}

f, ok := i.(*os.File)
if ok {
data, err := io.ReadAll(f)
if err != nil {
return err
}
wHeader.Set("Content-Type", http.DetectContentType(data))
wHeader.Set("Content-Disposition", "attachment; filename="+f.Name())
if status != nil {
w.WriteHeader(*status)
} else {
w.WriteHeader(http.StatusOK)
}
_, err = w.Write(data)
return err
}
wHeader.Set("Content-Type", "application/json; charset=UTF-8")

if status != nil {
w.WriteHeader(*status)
} else {
w.WriteHeader(http.StatusOK)
}

if i != nil {
return json.NewEncoder(w).Encode(i)
}

return nil
}

// ReadFormFileToTempFile reads file data from a request form and writes it to a temporary file
func ReadFormFileToTempFile(r *http.Request, key string) (*os.File, error) {
_, fileHeader, err := r.FormFile(key)
if err != nil {
return nil, err
}

return readFileHeaderToTempFile(fileHeader)
}

// ReadFormFilesToTempFiles reads files array data from a request form and writes it to a temporary files
func ReadFormFilesToTempFiles(r *http.Request, key string) ([]*os.File, error) {
if err := r.ParseMultipartForm(32 << 20); err != nil {
return nil, err
}

files := make([]*os.File, 0, len(r.MultipartForm.File[key]))

for _, fileHeader := range r.MultipartForm.File[key] {
file, err := readFileHeaderToTempFile(fileHeader)
if err != nil {
return nil, err
}

files = append(files, file)
}

return files, nil
}

// readFileHeaderToTempFile reads multipart.FileHeader and writes it to a temporary file
func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error) {
formFile, err := fileHeader.Open()
if err != nil {
return nil, err
}

defer formFile.Close()

// Use .* as suffix, because the asterisk is a placeholder for the random value,
// and the period allows consumers of this file to remove the suffix to obtain the original file name
file, err := os.CreateTemp("", fileHeader.Filename+".*")
if err != nil {
return nil, err
}

defer file.Close()

_, err = io.Copy(file, formFile)
if err != nil {
return nil, err
}

return file, nil
}

func parseTimes(param string) ([]time.Time, error) {
splits := strings.Split(param, ",")
times := make([]time.Time, 0, len(splits))
for _, v := range splits {
t, err := parseTime(v)
if err != nil {
return nil, err
}
times = append(times, t)
}
return times, nil
}

// parseTime will parses a string parameter into a time.Time using the RFC3339 format
func parseTime(param string) (time.Time, error) {
if param == "" {
return time.Time{}, nil
}
return time.Parse(time.RFC3339, param)
}

type Number interface {
~int32 | ~int64 | ~float32 | ~float64
}

type ParseString[T Number | string | bool] func(v string) (T, error)

// parseFloat64 parses a string parameter to an float64.
func parseFloat64(param string) (float64, error) {
if param == "" {
return 0, nil
}

return strconv.ParseFloat(param, 64)
}

// parseFloat32 parses a string parameter to an float32.
func parseFloat32(param string) (float32, error) {
if param == "" {
return 0, nil
}

v, err := strconv.ParseFloat(param, 32)
return float32(v), err
}

// parseInt64 parses a string parameter to an int64.
func parseInt64(param string) (int64, error) {
if param == "" {
return 0, nil
}

return strconv.ParseInt(param, 10, 64)
}

// parseInt32 parses a string parameter to an int32.
func parseInt32(param string) (int32, error) {
if param == "" {
return 0, nil
}

val, err := strconv.ParseInt(param, 10, 32)
return int32(val), err
}

// parseBool parses a string parameter to an bool.
func parseBool(param string) (bool, error) {
if param == "" {
return false, nil
}

return strconv.ParseBool(param)
}

type Operation[T Number | string | bool] func(actual string) (T, bool, error)

func WithRequire[T Number | string | bool](parse ParseString[T]) Operation[T] {
var empty T
return func(actual string) (T, bool, error) {
if actual == "" {
return empty, false, errors.New(errMsgRequiredMissing)
}

v, err := parse(actual)
return v, false, err
}
}

func WithDefaultOrParse[T Number | string | bool](def T, parse ParseString[T]) Operation[T] {
return func(actual string) (T, bool, error) {
if actual == "" {
return def, true, nil
}

v, err := parse(actual)
return v, false, err
}
}

func WithParse[T Number | string | bool](parse ParseString[T]) Operation[T] {
return func(actual string) (T, bool, error) {
v, err := parse(actual)
return v, false, err
}
}

type Constraint[T Number | string | bool] func(actual T) error

func WithMinimum[T Number](expected T) Constraint[T] {
return func(actual T) error {
if actual < expected {
return errors.New(errMsgMinValueConstraint)
}

return nil
}
}

func WithMaximum[T Number](expected T) Constraint[T] {
return func(actual T) error {
if actual > expected {
return errors.New(errMsgMaxValueConstraint)
}

return nil
}
}

// parseNumericParameter parses a numeric parameter to its respective type.
func parseNumericParameter[T Number](param string, fn Operation[T], checks ...Constraint[T]) (T, error) {
v, ok, err := fn(param)
if err != nil {
return 0, err
}

if !ok {
for _, check := range checks {
if err := check(v); err != nil {
return 0, err
}
}
}

return v, nil
}

// parseBoolParameter parses a string parameter to a bool
func parseBoolParameter(param string, fn Operation[bool]) (bool, error) {
v, _, err := fn(param)
return v, err
}

// parseNumericArrayParameter parses a string parameter containing array of values to its respective type.
func parseNumericArrayParameter[T Number](param, delim string, required bool, fn Operation[T], checks ...Constraint[T]) ([]T, error) {
if param == "" {
if required {
return nil, errors.New(errMsgRequiredMissing)
}

return nil, nil
}

str := strings.Split(param, delim)
values := make([]T, len(str))

for i, s := range str {
v, ok, err := fn(s)
if err != nil {
return nil, err
}

if !ok {
for _, check := range checks {
if err := check(v); err != nil {
return nil, err
}
}
}

values[i] = v
}

return values, nil
}

// parseQuery parses query parameters and returns an error if any malformed value pairs are encountered.
func parseQuery(rawQuery string) (url.Values, error) {
return url.ParseQuery(rawQuery)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,20 @@
package {{packageName}}

import (
"log"
"net/http"
{{#routers}}
{{#mux}}
"log"
"time"
{{/mux}}
{{#chi}}
"github.com/go-chi/chi/v5/middleware"
{{/chi}}
{{/routers}}
)

{{#routers}}
{{#mux}}
func Logger(inner http.Handler, name string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
Expand All @@ -22,3 +31,10 @@ func Logger(inner http.Handler, name string) http.Handler {
)
})
}
{{/mux}}
{{#chi}}
func Logger(inner http.Handler) http.Handler {
return middleware.Logger(inner)
}
{{/chi}}
{{/routers}}
Loading
Loading