diff --git a/ginext/response.go b/ginext/response.go index e3d9e98..c16d816 100644 --- a/ginext/response.go +++ b/ginext/response.go @@ -1,13 +1,8 @@ package ginext import ( - "context" - "fmt" "github.com/gin-gonic/gin" "gogs.mikescher.com/BlackForestBytes/goext/exerr" - json "gogs.mikescher.com/BlackForestBytes/goext/gojson" - "gogs.mikescher.com/BlackForestBytes/goext/langext" - "os" ) type cookieval struct { @@ -41,464 +36,6 @@ type InspectableHTTPResponse interface { Headers() []string } -type jsonHTTPResponse struct { - statusCode int - data any - headers []headerval - cookies []cookieval -} - -func (j jsonHTTPResponse) jsonRenderer(g *gin.Context) json.GoJsonRender { - var f *string - if jsonfilter := g.GetString("goext.jsonfilter"); jsonfilter != "" { - f = &jsonfilter - } - return json.GoJsonRender{Data: j.data, NilSafeSlices: true, NilSafeMaps: true, Filter: f} -} - -func (j jsonHTTPResponse) Write(g *gin.Context) { - for _, v := range j.headers { - g.Header(v.Key, v.Val) - } - for _, v := range j.cookies { - g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly) - } - g.Render(j.statusCode, j.jsonRenderer(g)) -} - -func (j jsonHTTPResponse) WithHeader(k string, v string) HTTPResponse { - j.headers = append(j.headers, headerval{k, v}) - return j -} - -func (j jsonHTTPResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse { - j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly}) - return j -} - -func (j jsonHTTPResponse) IsSuccess() bool { - return j.statusCode >= 200 && j.statusCode <= 399 -} - -func (j jsonHTTPResponse) Statuscode() int { - return j.statusCode -} - -func (j jsonHTTPResponse) BodyString(g *gin.Context) *string { - if str, err := j.jsonRenderer(g).RenderString(); err == nil { - return &str - } else { - return nil - } -} - -func (j jsonHTTPResponse) ContentType() string { - return "application/json" -} - -func (j jsonHTTPResponse) Headers() []string { - return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) -} - -type emptyHTTPResponse struct { - statusCode int - headers []headerval - cookies []cookieval -} - -func (j emptyHTTPResponse) Write(g *gin.Context) { - for _, v := range j.headers { - g.Header(v.Key, v.Val) - } - for _, v := range j.cookies { - g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly) - } - g.Status(j.statusCode) -} - -func (j emptyHTTPResponse) WithHeader(k string, v string) HTTPResponse { - j.headers = append(j.headers, headerval{k, v}) - return j -} - -func (j emptyHTTPResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse { - j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly}) - return j -} - -func (j emptyHTTPResponse) IsSuccess() bool { - return j.statusCode >= 200 && j.statusCode <= 399 -} - -func (j emptyHTTPResponse) Statuscode() int { - return j.statusCode -} - -func (j emptyHTTPResponse) BodyString(*gin.Context) *string { - return nil -} - -func (j emptyHTTPResponse) ContentType() string { - return "" -} - -func (j emptyHTTPResponse) Headers() []string { - return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) -} - -type textHTTPResponse struct { - statusCode int - data string - headers []headerval - cookies []cookieval -} - -func (j textHTTPResponse) Write(g *gin.Context) { - for _, v := range j.headers { - g.Header(v.Key, v.Val) - } - for _, v := range j.cookies { - g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly) - } - g.String(j.statusCode, "%s", j.data) -} - -func (j textHTTPResponse) WithHeader(k string, v string) HTTPResponse { - j.headers = append(j.headers, headerval{k, v}) - return j -} - -func (j textHTTPResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse { - j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly}) - return j -} - -func (j textHTTPResponse) IsSuccess() bool { - return j.statusCode >= 200 && j.statusCode <= 399 -} - -func (j textHTTPResponse) Statuscode() int { - return j.statusCode -} - -func (j textHTTPResponse) BodyString(*gin.Context) *string { - return langext.Ptr(j.data) -} - -func (j textHTTPResponse) ContentType() string { - return "text/plain" -} - -func (j textHTTPResponse) Headers() []string { - return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) -} - -type dataHTTPResponse struct { - statusCode int - data []byte - contentType string - headers []headerval - cookies []cookieval -} - -func (j dataHTTPResponse) Write(g *gin.Context) { - for _, v := range j.headers { - g.Header(v.Key, v.Val) - } - for _, v := range j.cookies { - g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly) - } - g.Data(j.statusCode, j.contentType, j.data) -} - -func (j dataHTTPResponse) WithHeader(k string, v string) HTTPResponse { - j.headers = append(j.headers, headerval{k, v}) - return j -} - -func (j dataHTTPResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse { - j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly}) - return j -} - -func (j dataHTTPResponse) IsSuccess() bool { - return j.statusCode >= 200 && j.statusCode <= 399 -} - -func (j dataHTTPResponse) Statuscode() int { - return j.statusCode -} - -func (j dataHTTPResponse) BodyString(*gin.Context) *string { - return langext.Ptr(string(j.data)) -} - -func (j dataHTTPResponse) ContentType() string { - return j.contentType -} - -func (j dataHTTPResponse) Headers() []string { - return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) -} - -type fileHTTPResponse struct { - mimetype string - filepath string - filename *string - headers []headerval - cookies []cookieval -} - -func (j fileHTTPResponse) Write(g *gin.Context) { - g.Header("Content-Type", j.mimetype) // if we don't set it here gin does weird file-sniffing later... - if j.filename != nil { - g.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", *j.filename)) - - } - for _, v := range j.headers { - g.Header(v.Key, v.Val) - } - for _, v := range j.cookies { - g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly) - } - g.File(j.filepath) -} - -func (j fileHTTPResponse) WithHeader(k string, v string) HTTPResponse { - j.headers = append(j.headers, headerval{k, v}) - return j -} - -func (j fileHTTPResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse { - j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly}) - return j -} - -func (j fileHTTPResponse) IsSuccess() bool { - return true -} - -func (j fileHTTPResponse) Statuscode() int { - return 200 -} - -func (j fileHTTPResponse) BodyString(*gin.Context) *string { - data, err := os.ReadFile(j.filepath) - if err != nil { - return nil - } - return langext.Ptr(string(data)) -} - -func (j fileHTTPResponse) ContentType() string { - return j.mimetype -} - -func (j fileHTTPResponse) Headers() []string { - return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) -} - -type downloadDataHTTPResponse struct { - statusCode int - mimetype string - data []byte - filename *string - headers []headerval - cookies []cookieval -} - -func (j downloadDataHTTPResponse) Write(g *gin.Context) { - g.Header("Content-Type", j.mimetype) // if we don't set it here gin does weird file-sniffing later... - if j.filename != nil { - g.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", *j.filename)) - } - for _, v := range j.headers { - g.Header(v.Key, v.Val) - } - for _, v := range j.cookies { - g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly) - } - g.Data(j.statusCode, j.mimetype, j.data) -} - -func (j downloadDataHTTPResponse) WithHeader(k string, v string) HTTPResponse { - j.headers = append(j.headers, headerval{k, v}) - return j -} - -func (j downloadDataHTTPResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse { - j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly}) - return j -} - -func (j downloadDataHTTPResponse) IsSuccess() bool { - return j.statusCode >= 200 && j.statusCode <= 399 -} - -func (j downloadDataHTTPResponse) Statuscode() int { - return j.statusCode -} - -func (j downloadDataHTTPResponse) BodyString(*gin.Context) *string { - return langext.Ptr(string(j.data)) -} - -func (j downloadDataHTTPResponse) ContentType() string { - return j.mimetype -} - -func (j downloadDataHTTPResponse) Headers() []string { - return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) -} - -type redirectHTTPResponse struct { - statusCode int - url string - headers []headerval - cookies []cookieval -} - -func (j redirectHTTPResponse) Write(g *gin.Context) { - for _, v := range j.headers { - g.Header(v.Key, v.Val) - } - for _, v := range j.cookies { - g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly) - } - g.Redirect(j.statusCode, j.url) -} - -func (j redirectHTTPResponse) WithHeader(k string, v string) HTTPResponse { - j.headers = append(j.headers, headerval{k, v}) - return j -} - -func (j redirectHTTPResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse { - j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly}) - return j -} - -func (j redirectHTTPResponse) IsSuccess() bool { - return j.statusCode >= 200 && j.statusCode <= 399 -} - -func (j redirectHTTPResponse) Statuscode() int { - return j.statusCode -} - -func (j redirectHTTPResponse) BodyString(*gin.Context) *string { - return nil -} - -func (j redirectHTTPResponse) ContentType() string { - return "" -} - -func (j redirectHTTPResponse) Headers() []string { - return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) -} - -type jsonAPIErrResponse struct { - err *exerr.ExErr - headers []headerval - cookies []cookieval -} - -func (j jsonAPIErrResponse) Write(g *gin.Context) { - for _, v := range j.headers { - g.Header(v.Key, v.Val) - } - for _, v := range j.cookies { - g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly) - } - - exerr.Get(j.err).Output(context.Background(), g) - - j.err.CallListener(exerr.MethodOutput) -} - -func (j jsonAPIErrResponse) WithHeader(k string, v string) HTTPResponse { - j.headers = append(j.headers, headerval{k, v}) - return j -} - -func (j jsonAPIErrResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse { - j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly}) - return j -} - -func (j jsonAPIErrResponse) IsSuccess() bool { - return false -} - -func (j jsonAPIErrResponse) Statuscode() int { - return langext.Coalesce(j.err.RecursiveStatuscode(), 0) -} - -func (j jsonAPIErrResponse) BodyString(*gin.Context) *string { - if str, err := j.err.ToDefaultAPIJson(); err == nil { - return &str - } else { - return nil - } -} - -func (j jsonAPIErrResponse) ContentType() string { - return "application/json" -} - -func (j jsonAPIErrResponse) Headers() []string { - return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) -} - -func (j jsonAPIErrResponse) Unwrap() error { - return j.err -} - -func Status(sc int) HTTPResponse { - return &emptyHTTPResponse{statusCode: sc} -} - -func JSON(sc int, data any) HTTPResponse { - return &jsonHTTPResponse{statusCode: sc, data: data} -} - -func Data(sc int, contentType string, data []byte) HTTPResponse { - return &dataHTTPResponse{statusCode: sc, contentType: contentType, data: data} -} - -func Text(sc int, data string) HTTPResponse { - return &textHTTPResponse{statusCode: sc, data: data} -} - -func File(mimetype string, filepath string) HTTPResponse { - return &fileHTTPResponse{mimetype: mimetype, filepath: filepath} -} - -func Download(mimetype string, filepath string, filename string) HTTPResponse { - return &fileHTTPResponse{mimetype: mimetype, filepath: filepath, filename: &filename} -} - -func DownloadData(status int, mimetype string, filename string, data []byte) HTTPResponse { - return &downloadDataHTTPResponse{statusCode: status, mimetype: mimetype, data: data, filename: &filename} -} - -func Redirect(sc int, newURL string) HTTPResponse { - return &redirectHTTPResponse{statusCode: sc, url: newURL} -} - -func Error(e error) HTTPResponse { - return &jsonAPIErrResponse{ - err: exerr.FromError(e), - } -} - -func ErrWrap(e error, errorType exerr.ErrorType, msg string) HTTPResponse { - return &jsonAPIErrResponse{ - err: exerr.FromError(exerr.Wrap(e, msg).WithType(errorType).Build()), - } -} - func NotImplemented() HTTPResponse { return Error(exerr.New(exerr.TypeNotImplemented, "").Build()) } diff --git a/ginext/responseData.go b/ginext/responseData.go new file mode 100644 index 0000000..dc5499c --- /dev/null +++ b/ginext/responseData.go @@ -0,0 +1,58 @@ +package ginext + +import ( + "github.com/gin-gonic/gin" + "gogs.mikescher.com/BlackForestBytes/goext/langext" +) + +type dataHTTPResponse struct { + statusCode int + data []byte + contentType string + headers []headerval + cookies []cookieval +} + +func (j dataHTTPResponse) Write(g *gin.Context) { + for _, v := range j.headers { + g.Header(v.Key, v.Val) + } + for _, v := range j.cookies { + g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly) + } + g.Data(j.statusCode, j.contentType, j.data) +} + +func (j dataHTTPResponse) WithHeader(k string, v string) HTTPResponse { + j.headers = append(j.headers, headerval{k, v}) + return j +} + +func (j dataHTTPResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse { + j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly}) + return j +} + +func (j dataHTTPResponse) IsSuccess() bool { + return j.statusCode >= 200 && j.statusCode <= 399 +} + +func (j dataHTTPResponse) Statuscode() int { + return j.statusCode +} + +func (j dataHTTPResponse) BodyString(*gin.Context) *string { + return langext.Ptr(string(j.data)) +} + +func (j dataHTTPResponse) ContentType() string { + return j.contentType +} + +func (j dataHTTPResponse) Headers() []string { + return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) +} + +func Data(sc int, contentType string, data []byte) HTTPResponse { + return &dataHTTPResponse{statusCode: sc, contentType: contentType, data: data} +} diff --git a/ginext/responseDownload.go b/ginext/responseDownload.go new file mode 100644 index 0000000..ad2a7a0 --- /dev/null +++ b/ginext/responseDownload.go @@ -0,0 +1,64 @@ +package ginext + +import ( + "fmt" + "github.com/gin-gonic/gin" + "gogs.mikescher.com/BlackForestBytes/goext/langext" +) + +type downloadDataHTTPResponse struct { + statusCode int + mimetype string + data []byte + filename *string + headers []headerval + cookies []cookieval +} + +func (j downloadDataHTTPResponse) Write(g *gin.Context) { + g.Header("Content-Type", j.mimetype) // if we don't set it here gin does weird file-sniffing later... + if j.filename != nil { + g.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", *j.filename)) + } + for _, v := range j.headers { + g.Header(v.Key, v.Val) + } + for _, v := range j.cookies { + g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly) + } + g.Data(j.statusCode, j.mimetype, j.data) +} + +func (j downloadDataHTTPResponse) WithHeader(k string, v string) HTTPResponse { + j.headers = append(j.headers, headerval{k, v}) + return j +} + +func (j downloadDataHTTPResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse { + j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly}) + return j +} + +func (j downloadDataHTTPResponse) IsSuccess() bool { + return j.statusCode >= 200 && j.statusCode <= 399 +} + +func (j downloadDataHTTPResponse) Statuscode() int { + return j.statusCode +} + +func (j downloadDataHTTPResponse) BodyString(*gin.Context) *string { + return langext.Ptr(string(j.data)) +} + +func (j downloadDataHTTPResponse) ContentType() string { + return j.mimetype +} + +func (j downloadDataHTTPResponse) Headers() []string { + return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) +} + +func DownloadData(status int, mimetype string, filename string, data []byte) HTTPResponse { + return &downloadDataHTTPResponse{statusCode: status, mimetype: mimetype, data: data, filename: &filename} +} diff --git a/ginext/responseEmpty.go b/ginext/responseEmpty.go new file mode 100644 index 0000000..7baa2b8 --- /dev/null +++ b/ginext/responseEmpty.go @@ -0,0 +1,56 @@ +package ginext + +import ( + "github.com/gin-gonic/gin" + "gogs.mikescher.com/BlackForestBytes/goext/langext" +) + +type emptyHTTPResponse struct { + statusCode int + headers []headerval + cookies []cookieval +} + +func (j emptyHTTPResponse) Write(g *gin.Context) { + for _, v := range j.headers { + g.Header(v.Key, v.Val) + } + for _, v := range j.cookies { + g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly) + } + g.Status(j.statusCode) +} + +func (j emptyHTTPResponse) WithHeader(k string, v string) HTTPResponse { + j.headers = append(j.headers, headerval{k, v}) + return j +} + +func (j emptyHTTPResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse { + j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly}) + return j +} + +func (j emptyHTTPResponse) IsSuccess() bool { + return j.statusCode >= 200 && j.statusCode <= 399 +} + +func (j emptyHTTPResponse) Statuscode() int { + return j.statusCode +} + +func (j emptyHTTPResponse) BodyString(*gin.Context) *string { + return nil +} + +func (j emptyHTTPResponse) ContentType() string { + return "" +} + +func (j emptyHTTPResponse) Headers() []string { + return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) +} + +func Status(sc int) HTTPResponse { + return &emptyHTTPResponse{statusCode: sc} +} diff --git a/ginext/responseFile.go b/ginext/responseFile.go new file mode 100644 index 0000000..0025489 --- /dev/null +++ b/ginext/responseFile.go @@ -0,0 +1,73 @@ +package ginext + +import ( + "fmt" + "github.com/gin-gonic/gin" + "gogs.mikescher.com/BlackForestBytes/goext/langext" + "os" +) + +type fileHTTPResponse struct { + mimetype string + filepath string + filename *string + headers []headerval + cookies []cookieval +} + +func (j fileHTTPResponse) Write(g *gin.Context) { + g.Header("Content-Type", j.mimetype) // if we don't set it here gin does weird file-sniffing later... + if j.filename != nil { + g.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", *j.filename)) + + } + for _, v := range j.headers { + g.Header(v.Key, v.Val) + } + for _, v := range j.cookies { + g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly) + } + g.File(j.filepath) +} + +func (j fileHTTPResponse) WithHeader(k string, v string) HTTPResponse { + j.headers = append(j.headers, headerval{k, v}) + return j +} + +func (j fileHTTPResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse { + j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly}) + return j +} + +func (j fileHTTPResponse) IsSuccess() bool { + return true +} + +func (j fileHTTPResponse) Statuscode() int { + return 200 +} + +func (j fileHTTPResponse) BodyString(*gin.Context) *string { + data, err := os.ReadFile(j.filepath) + if err != nil { + return nil + } + return langext.Ptr(string(data)) +} + +func (j fileHTTPResponse) ContentType() string { + return j.mimetype +} + +func (j fileHTTPResponse) Headers() []string { + return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) +} + +func File(mimetype string, filepath string) HTTPResponse { + return &fileHTTPResponse{mimetype: mimetype, filepath: filepath} +} + +func Download(mimetype string, filepath string, filename string) HTTPResponse { + return &fileHTTPResponse{mimetype: mimetype, filepath: filepath, filename: &filename} +} diff --git a/ginext/responseJson.go b/ginext/responseJson.go new file mode 100644 index 0000000..5832df9 --- /dev/null +++ b/ginext/responseJson.go @@ -0,0 +1,70 @@ +package ginext + +import ( + "github.com/gin-gonic/gin" + json "gogs.mikescher.com/BlackForestBytes/goext/gojson" + "gogs.mikescher.com/BlackForestBytes/goext/langext" +) + +type jsonHTTPResponse struct { + statusCode int + data any + headers []headerval + cookies []cookieval +} + +func (j jsonHTTPResponse) jsonRenderer(g *gin.Context) json.GoJsonRender { + var f *string + if jsonfilter := g.GetString("goext.jsonfilter"); jsonfilter != "" { + f = &jsonfilter + } + return json.GoJsonRender{Data: j.data, NilSafeSlices: true, NilSafeMaps: true, Filter: f} +} + +func (j jsonHTTPResponse) Write(g *gin.Context) { + for _, v := range j.headers { + g.Header(v.Key, v.Val) + } + for _, v := range j.cookies { + g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly) + } + g.Render(j.statusCode, j.jsonRenderer(g)) +} + +func (j jsonHTTPResponse) WithHeader(k string, v string) HTTPResponse { + j.headers = append(j.headers, headerval{k, v}) + return j +} + +func (j jsonHTTPResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse { + j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly}) + return j +} + +func (j jsonHTTPResponse) IsSuccess() bool { + return j.statusCode >= 200 && j.statusCode <= 399 +} + +func (j jsonHTTPResponse) Statuscode() int { + return j.statusCode +} + +func (j jsonHTTPResponse) BodyString(g *gin.Context) *string { + if str, err := j.jsonRenderer(g).RenderString(); err == nil { + return &str + } else { + return nil + } +} + +func (j jsonHTTPResponse) ContentType() string { + return "application/json" +} + +func (j jsonHTTPResponse) Headers() []string { + return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) +} + +func JSON(sc int, data any) HTTPResponse { + return &jsonHTTPResponse{statusCode: sc, data: data} +} diff --git a/ginext/responseJsonAPI.go b/ginext/responseJsonAPI.go new file mode 100644 index 0000000..e0dea10 --- /dev/null +++ b/ginext/responseJsonAPI.go @@ -0,0 +1,77 @@ +package ginext + +import ( + "context" + "github.com/gin-gonic/gin" + "gogs.mikescher.com/BlackForestBytes/goext/exerr" + "gogs.mikescher.com/BlackForestBytes/goext/langext" +) + +type jsonAPIErrResponse struct { + err *exerr.ExErr + headers []headerval + cookies []cookieval +} + +func (j jsonAPIErrResponse) Write(g *gin.Context) { + for _, v := range j.headers { + g.Header(v.Key, v.Val) + } + for _, v := range j.cookies { + g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly) + } + + exerr.Get(j.err).Output(context.Background(), g) + + j.err.CallListener(exerr.MethodOutput) +} + +func (j jsonAPIErrResponse) WithHeader(k string, v string) HTTPResponse { + j.headers = append(j.headers, headerval{k, v}) + return j +} + +func (j jsonAPIErrResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse { + j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly}) + return j +} + +func (j jsonAPIErrResponse) IsSuccess() bool { + return false +} + +func (j jsonAPIErrResponse) Statuscode() int { + return langext.Coalesce(j.err.RecursiveStatuscode(), 0) +} + +func (j jsonAPIErrResponse) BodyString(*gin.Context) *string { + if str, err := j.err.ToDefaultAPIJson(); err == nil { + return &str + } else { + return nil + } +} + +func (j jsonAPIErrResponse) ContentType() string { + return "application/json" +} + +func (j jsonAPIErrResponse) Headers() []string { + return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) +} + +func (j jsonAPIErrResponse) Unwrap() error { + return j.err +} + +func Error(e error) HTTPResponse { + return &jsonAPIErrResponse{ + err: exerr.FromError(e), + } +} + +func ErrWrap(e error, errorType exerr.ErrorType, msg string) HTTPResponse { + return &jsonAPIErrResponse{ + err: exerr.FromError(exerr.Wrap(e, msg).WithType(errorType).Build()), + } +} diff --git a/ginext/responseRedirect.go b/ginext/responseRedirect.go new file mode 100644 index 0000000..4b401a9 --- /dev/null +++ b/ginext/responseRedirect.go @@ -0,0 +1,57 @@ +package ginext + +import ( + "github.com/gin-gonic/gin" + "gogs.mikescher.com/BlackForestBytes/goext/langext" +) + +type redirectHTTPResponse struct { + statusCode int + url string + headers []headerval + cookies []cookieval +} + +func (j redirectHTTPResponse) Write(g *gin.Context) { + for _, v := range j.headers { + g.Header(v.Key, v.Val) + } + for _, v := range j.cookies { + g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly) + } + g.Redirect(j.statusCode, j.url) +} + +func (j redirectHTTPResponse) WithHeader(k string, v string) HTTPResponse { + j.headers = append(j.headers, headerval{k, v}) + return j +} + +func (j redirectHTTPResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse { + j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly}) + return j +} + +func (j redirectHTTPResponse) IsSuccess() bool { + return j.statusCode >= 200 && j.statusCode <= 399 +} + +func (j redirectHTTPResponse) Statuscode() int { + return j.statusCode +} + +func (j redirectHTTPResponse) BodyString(*gin.Context) *string { + return nil +} + +func (j redirectHTTPResponse) ContentType() string { + return "" +} + +func (j redirectHTTPResponse) Headers() []string { + return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) +} + +func Redirect(sc int, newURL string) HTTPResponse { + return &redirectHTTPResponse{statusCode: sc, url: newURL} +} diff --git a/ginext/responseSeekable.go b/ginext/responseSeekable.go new file mode 100644 index 0000000..d754212 --- /dev/null +++ b/ginext/responseSeekable.go @@ -0,0 +1,72 @@ +package ginext + +import ( + "github.com/gin-gonic/gin" + "gogs.mikescher.com/BlackForestBytes/goext/exerr" + "gogs.mikescher.com/BlackForestBytes/goext/langext" + "io" + "net/http" + "time" +) + +type seekableResponse struct { + data io.ReadSeeker + contentType string + filename string + headers []headerval + cookies []cookieval +} + +func (j seekableResponse) Write(g *gin.Context) { + g.Header("Content-Type", j.contentType) // if we don't set it here http.ServeContent does weird sniffing later... + + for _, v := range j.headers { + g.Header(v.Key, v.Val) + } + for _, v := range j.cookies { + g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly) + } + + http.ServeContent(g.Writer, g.Request, j.filename, time.Unix(0, 0), j.data) + + if clsr, ok := j.data.(io.ReadSeekCloser); ok { + err := clsr.Close() + if err != nil { + exerr.Wrap(err, "failed to close io.ReadSeerkClose in ginext.Seekable").Str("filename", j.filename).Print() + } + } +} + +func (j seekableResponse) WithHeader(k string, v string) HTTPResponse { + j.headers = append(j.headers, headerval{k, v}) + return j +} + +func (j seekableResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse { + j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly}) + return j +} + +func (j seekableResponse) IsSuccess() bool { + return true +} + +func (j seekableResponse) Statuscode() int { + return 200 +} + +func (j seekableResponse) BodyString(*gin.Context) *string { + return langext.Ptr("(seekable)") +} + +func (j seekableResponse) ContentType() string { + return j.contentType +} + +func (j seekableResponse) Headers() []string { + return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) +} + +func Seekable(filename string, contentType string, data io.ReadSeeker) HTTPResponse { + return &seekableResponse{filename: filename, contentType: contentType, data: data} +} diff --git a/ginext/responseText.go b/ginext/responseText.go new file mode 100644 index 0000000..83812ed --- /dev/null +++ b/ginext/responseText.go @@ -0,0 +1,57 @@ +package ginext + +import ( + "github.com/gin-gonic/gin" + "gogs.mikescher.com/BlackForestBytes/goext/langext" +) + +type textHTTPResponse struct { + statusCode int + data string + headers []headerval + cookies []cookieval +} + +func (j textHTTPResponse) Write(g *gin.Context) { + for _, v := range j.headers { + g.Header(v.Key, v.Val) + } + for _, v := range j.cookies { + g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly) + } + g.String(j.statusCode, "%s", j.data) +} + +func (j textHTTPResponse) WithHeader(k string, v string) HTTPResponse { + j.headers = append(j.headers, headerval{k, v}) + return j +} + +func (j textHTTPResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse { + j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly}) + return j +} + +func (j textHTTPResponse) IsSuccess() bool { + return j.statusCode >= 200 && j.statusCode <= 399 +} + +func (j textHTTPResponse) Statuscode() int { + return j.statusCode +} + +func (j textHTTPResponse) BodyString(*gin.Context) *string { + return langext.Ptr(j.data) +} + +func (j textHTTPResponse) ContentType() string { + return "text/plain" +} + +func (j textHTTPResponse) Headers() []string { + return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) +} + +func Text(sc int, data string) HTTPResponse { + return &textHTTPResponse{statusCode: sc, data: data} +} diff --git a/go.mod b/go.mod index fcc5d66..2f7749a 100644 --- a/go.mod +++ b/go.mod @@ -9,9 +9,9 @@ require ( github.com/rs/xid v1.5.0 github.com/rs/zerolog v1.33.0 go.mongodb.org/mongo-driver v1.15.0 - golang.org/x/crypto v0.23.0 - golang.org/x/sys v0.20.0 - golang.org/x/term v0.20.0 + golang.org/x/crypto v0.24.0 + golang.org/x/sys v0.21.0 + golang.org/x/term v0.21.0 ) require ( @@ -38,7 +38,7 @@ require ( github.com/google/uuid v1.5.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.8 // indirect - github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -54,10 +54,10 @@ require ( github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240424034433-3c2c7870ae76 // indirect golang.org/x/arch v0.8.0 // indirect - golang.org/x/image v0.16.0 // indirect - golang.org/x/net v0.25.0 // indirect - golang.org/x/text v0.15.0 // indirect - google.golang.org/protobuf v1.34.1 // indirect + golang.org/x/image v0.17.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/text v0.16.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/libc v1.37.6 // indirect modernc.org/mathutil v1.6.0 // indirect diff --git a/go.sum b/go.sum index ef286bb..f8d4816 100644 --- a/go.sum +++ b/go.sum @@ -125,6 +125,8 @@ github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/4 github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= @@ -235,11 +237,15 @@ golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw= golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs= +golang.org/x/image v0.17.0 h1:nTRVVdajgB8zCMZVsViyzhnMKPwYeroEERRC64JuLco= +golang.org/x/image v0.17.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -258,6 +264,8 @@ golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= @@ -285,6 +293,8 @@ golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= @@ -297,6 +307,8 @@ golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -307,6 +319,8 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -322,6 +336,8 @@ google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/goextVersion.go b/goextVersion.go index bbbcbf3..b714638 100644 --- a/goextVersion.go +++ b/goextVersion.go @@ -1,5 +1,5 @@ package goext -const GoextVersion = "0.0.466" +const GoextVersion = "0.0.469" -const GoextVersionTimestamp = "2024-06-03T13:48:30+0200" +const GoextVersionTimestamp = "2024-06-11T12:10:49+0200"