Compare commits

..

No commits in common. "main" and "v1.8.0" have entirely different histories.
main ... v1.8.0

9 changed files with 30 additions and 149 deletions

View file

@ -1,4 +1,4 @@
package webutils package http
import ( import (
"net/http" "net/http"

View file

@ -1,4 +1,4 @@
package webutils package http
import ( import (
"context" "context"

View file

@ -1,7 +1,6 @@
package webutils package http
import ( import (
"encoding/json"
"fmt" "fmt"
"net/http" "net/http"
) )
@ -10,63 +9,8 @@ import (
// The error will have the given return code `code` // The error will have the given return code `code`
// and a json encoded body with the field "id" set to `errId` // and a json encoded body with the field "id" set to `errId`
// and a field "message" set to the `message` // and a field "message" set to the `message`
//
// Deprecated: Use ProblemDetails or ProblemDetailsStatusOnly instead
func HttpErr(w http.ResponseWriter, errId int, message string, code int) { func HttpErr(w http.ResponseWriter, errId int, message string, code int) {
w.WriteHeader(code) w.WriteHeader(code)
w.Header().Add("Content-Type", "application/json") w.Header().Add("Content-Type", "application/json")
fmt.Fprintf(w, "{\"id\": %d, \"message\": \"%s\"}", errId, message) fmt.Fprintf(w, "{\"id\": %d, \"message\": \"%s\"}", errId, message)
} }
// Write an RFC 9457 compliant problem details response
// If details is not nil, it will be included.
// If extras is not nil, each key-value pair will be included at
// the root layer.
// Keys in extras that would overwrite the default elements will be ignored.
// Those would be "type", "status", "title" and "detail"
func ProblemDetails(
w http.ResponseWriter,
statusCode int,
errorType string,
errorTitle string,
details *string,
extras map[string]any,
) {
w.Header().Add("Content-Type", "application/problem+json")
w.WriteHeader(statusCode)
data := map[string]any{
"type": errorType,
"status": statusCode,
"title": errorTitle,
}
if details != nil {
data["detail"] = *details
}
if extras != nil {
for k, v := range extras {
if _, ok := data[k]; ok {
// Don't overwrite default fields
continue
}
data[k] = v
}
}
enc := json.NewEncoder(w)
enc.Encode(data)
}
// Write a simple problem details response.
// It only provides the status code, as defined in RFC 9457, section 4.2.1
func ProblemDetailsStatusOnly(w http.ResponseWriter, statusCode int) {
w.Header().Add("Content-Type", "application/problem+json")
w.WriteHeader(statusCode)
data := map[string]any{
"type": "about:blank",
"title": http.StatusText(statusCode),
"status": statusCode,
"reference": "RFC 9457",
}
enc := json.NewEncoder(w)
enc.Encode(data)
}

View file

@ -1,17 +0,0 @@
package webutils
import (
"encoding/json"
"fmt"
"net/http"
)
func SendJson(w http.ResponseWriter, data any) error {
encoded, err := json.Marshal(data)
if err != nil {
return err
}
w.Header().Add("Content-Type", "application/json")
fmt.Fprint(w, string(encoded))
return nil
}

View file

@ -1,4 +1,4 @@
package webutils package http
import ( import (
"net/http" "net/http"
@ -9,33 +9,6 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
func BuildLoggingMiddleware(extras map[string]string) HandlerBuilder {
return func(h http.Handler) http.Handler {
return ChainMiddlewares(h,
hlog.NewHandler(log.Logger),
hlog.AccessHandler(func(r *http.Request, status, size int, duration time.Duration) {
if strings.HasPrefix(r.URL.Path, "/assets") {
return
}
logger := hlog.FromRequest(r).Info().
Str("method", r.Method).
Stringer("url", r.URL).
Int("status", status).
Int("size", size).
Dur("duration", duration)
for k, v := range extras {
logger = logger.Str(k, v)
}
logger.Send()
}),
hlog.RemoteAddrHandler("ip"),
hlog.UserAgentHandler("user_agent"),
hlog.RefererHandler("referer"),
hlog.RequestIDHandler("req_id", "Request-Id"),
)
}
}
func LoggingMiddleware(handler http.Handler) http.Handler { func LoggingMiddleware(handler http.Handler) http.Handler {
return ChainMiddlewares(handler, return ChainMiddlewares(handler,
hlog.NewHandler(log.Logger), hlog.NewHandler(log.Logger),

View file

@ -4,7 +4,6 @@ package logrotate
import ( import (
"os" "os"
"path"
"sync" "sync"
"time" "time"
) )
@ -16,13 +15,13 @@ type RotateWriter struct {
} }
// Make a new RotateWriter. Return nil if error occurs during setup. // Make a new RotateWriter. Return nil if error occurs during setup.
func New(filename string) (*RotateWriter, error) { func New(filename string) *RotateWriter {
w := &RotateWriter{filename: filename} w := &RotateWriter{filename: filename}
err := w.Rotate() err := w.Rotate()
if err != nil { if err != nil {
return nil, err return nil
} }
return w, nil return w
} }
// Write satisfies the io.Writer interface. // Write satisfies the io.Writer interface.
@ -55,13 +54,6 @@ func (w *RotateWriter) Rotate() (err error) {
} }
// Create a file. // Create a file.
dir := path.Dir(w.filename)
_, err = os.Stat(dir)
if err != nil {
if err = os.Mkdir(dir, os.ModeDir); err != nil {
return
}
}
w.fp, err = os.Create(w.filename) w.fp, err = os.Create(w.filename)
return return
} }

View file

@ -1,13 +0,0 @@
package mathutils
type SignedNumber interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~float32 | ~float64
}
func Abs[T SignedNumber](num T) T {
if num > 0 {
return num
} else {
return num * -1
}
}

View file

@ -12,6 +12,7 @@ import (
var cliFlagLogLevel = "info" var cliFlagLogLevel = "info"
var cliFlagLogJson = false var cliFlagLogJson = false
var cliFlagLogFile = ""
func SetupFlags() { func SetupFlags() {
flag.StringVar( flag.StringVar(
@ -26,13 +27,16 @@ func SetupFlags() {
false, false,
"Log json objects to stderr instead of nicely formatted ones", "Log json objects to stderr instead of nicely formatted ones",
) )
flag.StringVar(
&cliFlagLogFile,
"logfile",
"",
"Set the file to write json formatted log messages to",
)
} }
// Configure logging. Utilises the flags setup in [SetupFlags]. func ConfigureLoggingFromCliArgs() {
// If logWriter is not nil, will also write logs, as json objects, configOutputs()
// to the given writer
func ConfigureLogging(logWriter io.Writer) {
configOutputs(logWriter)
configLevel() configLevel()
} }
@ -56,14 +60,22 @@ func configLevel() {
log.Info().Str("new-level", cliFlagLogLevel).Msg("New logging level set") log.Info().Str("new-level", cliFlagLogLevel).Msg("New logging level set")
} }
func configOutputs(logWriter io.Writer) { func configOutputs() {
console := zerolog.ConsoleWriter{Out: os.Stderr} extraLogWriters := []io.Writer{}
extraLogWriters := []io.Writer{console} if cliFlagLogFile != "" {
if logWriter != nil { file, err := os.OpenFile(cliFlagLogFile, os.O_APPEND|os.O_CREATE, os.ModeAppend)
extraLogWriters = append(extraLogWriters, logWriter) if err != nil {
log.Fatal().Err(err).Msg("Failed to open log file")
}
// NOTE: Technically this leaks a file handle
// Shouldn't matter though since the log file should be open until the very end
// Sooooo, eh. Don't care about adding an elaborate system to close the file at the very end.
// OS will do that anyway
extraLogWriters = append(extraLogWriters, file)
} }
if !cliFlagLogJson { if !cliFlagLogJson {
log.Logger = zerolog.New(zerolog.MultiLevelWriter(extraLogWriters...)). console := zerolog.ConsoleWriter{Out: os.Stderr}
log.Logger = zerolog.New(zerolog.MultiLevelWriter(append([]io.Writer{console}, extraLogWriters...)...)).
With(). With().
Timestamp(). Timestamp().
Logger() Logger()
@ -72,5 +84,4 @@ func configOutputs(logWriter io.Writer) {
append([]io.Writer{log.Logger}, extraLogWriters...)..., append([]io.Writer{log.Logger}, extraLogWriters...)...,
)).With().Timestamp().Logger() )).With().Timestamp().Logger()
} }
return
} }

View file

@ -81,15 +81,6 @@ func Contains[T comparable](a []T, b T) bool {
return false return false
} }
func ContainsFunc[T any](a []T, f func(t T) bool) bool {
for _, v := range a {
if f(v) {
return true
}
}
return false
}
func Compact[T any](a []T, compactor func(acc T, next T) T) T { func Compact[T any](a []T, compactor func(acc T, next T) T) T {
var acc T var acc T
for _, v := range a { for _, v := range a {