Compare commits

...

26 Commits

Author SHA1 Message Date
f6121a6961 v0.0.471 Revert "v0.0.470 Add GoextJsonMarshaller interface to call when marshalling json via gojson"
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 2m50s
2024-06-11 19:39:43 +02:00
7fc73f1e93 v0.0.470 Add GoextJsonMarshaller interface to call when marshalling json via gojson
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 2m51s
2024-06-11 19:34:48 +02:00
2504ef00a0 v0.0.469
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 3m26s
2024-06-11 12:10:49 +02:00
fc5803493c added DblPtrIfNotNil
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m31s
2024-06-05 17:53:57 +02:00
a9295bfabf added CoalesceDblPtr
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m37s
2024-06-05 15:10:31 +02:00
12fa53d848 v0.0.466 exerr.Wrap now inherits the Severity of the wrapped error
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m24s
2024-06-03 13:48:30 +02:00
d2bb362135 v0.0.465
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 3m39s
2024-06-03 09:39:57 +02:00
9dd81f6bd5 v0.0.464 improve ZeroLogErrTraces/ZeroLogAllTraces output for empty-message wrapped exerrs
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 4m36s
2024-06-01 02:40:48 +02:00
d2c04afcd5 v0.0.463 Fix SubtractYears
Some checks failed
Build Docker and Deploy / Run goext test-suite (push) Failing after 3m29s
2024-05-29 20:20:01 +02:00
62980e1489 v0.0.462
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 3m32s
2024-05-23 14:37:05 +02:00
59963adf74 v0.0.461
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m17s
2024-05-20 00:52:49 +02:00
194ea4ace5 v0.0.460
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m16s
2024-05-20 00:38:04 +02:00
73b80a66bc v0.0.459
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m16s
2024-05-20 00:20:31 +02:00
d8b2d01274 v0.0.458 revert 457 and fix ObjectFitImage
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 2m49s
2024-05-20 00:15:24 +02:00
bfa8457e95 v0.0.457 test
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 3m38s
2024-05-20 00:07:33 +02:00
70106733d9 v0.0.456
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 2m38s
2024-05-18 23:38:47 +02:00
ce7837b9ef v0.0.455 add proper json/bson marshalling to exerr [severity|type|category]
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 3m39s
2024-05-16 15:38:42 +02:00
d0d72167eb v0.0.454
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m17s
2024-05-14 15:10:27 +02:00
a55ee1a6ce v0.0.453
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m18s
2024-05-14 14:57:10 +02:00
dfc319573c v0.0.452
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 3m16s
2024-05-14 12:48:43 +02:00
246e555f3f v0.0.451 wpdf image processing
Some checks failed
Build Docker and Deploy / Run goext test-suite (push) Has been cancelled
2024-05-14 12:46:49 +02:00
c28bc086b2 v0.0.450 wpdf
Some checks failed
Build Docker and Deploy / Run goext test-suite (push) Failing after 3m33s
2024-05-14 11:52:56 +02:00
d44e971325 v0.0.449
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m15s
2024-05-12 16:51:52 +02:00
fe4cdc48af v0.0.448 wmo marshalHook
Some checks failed
Build Docker and Deploy / Run goext test-suite (push) Failing after 25s
2024-05-12 16:45:45 +02:00
631006a4e1 v0.0.447
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 3m18s
2024-05-10 21:33:01 +02:00
567ead8697 v0.0.446 syncMap.GetAndSetIfNotContains and CASMutex
Some checks failed
Build Docker and Deploy / Run goext test-suite (push) Has been cancelled
2024-05-10 21:31:36 +02:00
46 changed files with 3441 additions and 585 deletions

254
dataext/casMutex.go Normal file
View File

@@ -0,0 +1,254 @@
package dataext
import (
"context"
"golang.org/x/sync/semaphore"
"runtime"
"sync"
"sync/atomic"
"time"
"unsafe"
)
// from https://github.com/viney-shih/go-lock/blob/2f19fd8ce335e33e0ab9dccb1ff2ce820c3da332/cas.go
// CASMutex is the struct implementing RWMutex with CAS mechanism.
type CASMutex struct {
state casState
turnstile *semaphore.Weighted
broadcastChan chan struct{}
broadcastMut sync.RWMutex
}
func NewCASMutex() *CASMutex {
return &CASMutex{
state: casStateNoLock,
turnstile: semaphore.NewWeighted(1),
broadcastChan: make(chan struct{}),
}
}
type casState int32
const (
casStateUndefined casState = iota - 2 // -2
casStateWriteLock // -1
casStateNoLock // 0
casStateReadLock // >= 1
)
func (m *CASMutex) getState(n int32) casState {
switch st := casState(n); {
case st == casStateWriteLock:
fallthrough
case st == casStateNoLock:
return st
case st >= casStateReadLock:
return casStateReadLock
default:
// actually, it should not happened.
return casStateUndefined
}
}
func (m *CASMutex) listen() <-chan struct{} {
m.broadcastMut.RLock()
defer m.broadcastMut.RUnlock()
return m.broadcastChan
}
func (m *CASMutex) broadcast() {
newCh := make(chan struct{})
m.broadcastMut.Lock()
ch := m.broadcastChan
m.broadcastChan = newCh
m.broadcastMut.Unlock()
close(ch)
}
func (m *CASMutex) tryLock(ctx context.Context) bool {
for {
broker := m.listen()
if atomic.CompareAndSwapInt32(
(*int32)(unsafe.Pointer(&m.state)),
int32(casStateNoLock),
int32(casStateWriteLock),
) {
return true
}
if ctx == nil {
return false
}
select {
case <-ctx.Done():
// timeout or cancellation
return false
case <-broker:
// waiting for signal triggered by m.broadcast() and trying again.
}
}
}
// TryLockWithContext attempts to acquire the lock, blocking until resources
// are available or ctx is done (timeout or cancellation).
func (m *CASMutex) TryLockWithContext(ctx context.Context) bool {
if err := m.turnstile.Acquire(ctx, 1); err != nil {
// Acquire failed due to timeout or cancellation
return false
}
defer m.turnstile.Release(1)
return m.tryLock(ctx)
}
// Lock acquires the lock.
// If it is currently held by others, Lock will wait until it has a chance to acquire it.
func (m *CASMutex) Lock() {
ctx := context.Background()
m.TryLockWithContext(ctx)
}
// TryLock attempts to acquire the lock without blocking.
// Return false if someone is holding it now.
func (m *CASMutex) TryLock() bool {
if !m.turnstile.TryAcquire(1) {
return false
}
defer m.turnstile.Release(1)
return m.tryLock(nil)
}
// TryLockWithTimeout attempts to acquire the lock within a period of time.
// Return false if spending time is more than duration and no chance to acquire it.
func (m *CASMutex) TryLockWithTimeout(duration time.Duration) bool {
ctx, cancel := context.WithTimeout(context.Background(), duration)
defer cancel()
return m.TryLockWithContext(ctx)
}
// Unlock releases the lock.
func (m *CASMutex) Unlock() {
if ok := atomic.CompareAndSwapInt32(
(*int32)(unsafe.Pointer(&m.state)),
int32(casStateWriteLock),
int32(casStateNoLock),
); !ok {
panic("Unlock failed")
}
m.broadcast()
}
func (m *CASMutex) rTryLock(ctx context.Context) bool {
for {
broker := m.listen()
n := atomic.LoadInt32((*int32)(unsafe.Pointer(&m.state)))
st := m.getState(n)
switch st {
case casStateNoLock, casStateReadLock:
if atomic.CompareAndSwapInt32((*int32)(unsafe.Pointer(&m.state)), n, n+1) {
return true
}
}
if ctx == nil {
return false
}
select {
case <-ctx.Done():
// timeout or cancellation
return false
default:
switch st {
// read-lock failed due to concurrence issue, try again immediately
case casStateNoLock, casStateReadLock:
runtime.Gosched() // allow other goroutines to do stuff.
continue
}
}
select {
case <-ctx.Done():
// timeout or cancellation
return false
case <-broker:
// waiting for signal triggered by m.broadcast() and trying again.
}
}
}
// RTryLockWithContext attempts to acquire the read lock, blocking until resources
// are available or ctx is done (timeout or cancellation).
func (m *CASMutex) RTryLockWithContext(ctx context.Context) bool {
if err := m.turnstile.Acquire(ctx, 1); err != nil {
// Acquire failed due to timeout or cancellation
return false
}
m.turnstile.Release(1)
return m.rTryLock(ctx)
}
// RLock acquires the read lock.
// If it is currently held by others writing, RLock will wait until it has a chance to acquire it.
func (m *CASMutex) RLock() {
ctx := context.Background()
m.RTryLockWithContext(ctx)
}
// RTryLock attempts to acquire the read lock without blocking.
// Return false if someone is writing it now.
func (m *CASMutex) RTryLock() bool {
if !m.turnstile.TryAcquire(1) {
return false
}
m.turnstile.Release(1)
return m.rTryLock(nil)
}
// RTryLockWithTimeout attempts to acquire the read lock within a period of time.
// Return false if spending time is more than duration and no chance to acquire it.
func (m *CASMutex) RTryLockWithTimeout(duration time.Duration) bool {
ctx, cancel := context.WithTimeout(context.Background(), duration)
defer cancel()
return m.RTryLockWithContext(ctx)
}
// RUnlock releases the read lock.
func (m *CASMutex) RUnlock() {
n := atomic.AddInt32((*int32)(unsafe.Pointer(&m.state)), -1)
switch m.getState(n) {
case casStateUndefined, casStateWriteLock:
panic("RUnlock failed")
case casStateNoLock:
m.broadcast()
}
}
// RLocker returns a Locker interface that implements the Lock and Unlock methods
// by calling CASMutex.RLock and CASMutex.RUnlock.
func (m *CASMutex) RLocker() sync.Locker {
return (*rlocker)(m)
}
type rlocker CASMutex
func (r *rlocker) Lock() { (*CASMutex)(r).RLock() }
func (r *rlocker) Unlock() { (*CASMutex)(r).RUnlock() }

View File

@@ -35,6 +35,23 @@ func (s *SyncMap[TKey, TData]) SetIfNotContains(key TKey, data TData) bool {
return true return true
} }
func (s *SyncMap[TKey, TData]) SetIfNotContainsFunc(key TKey, data func() TData) bool {
s.lock.Lock()
defer s.lock.Unlock()
if s.data == nil {
s.data = make(map[TKey]TData)
}
if _, existsInPreState := s.data[key]; existsInPreState {
return false
}
s.data[key] = data()
return true
}
func (s *SyncMap[TKey, TData]) Get(key TKey) (TData, bool) { func (s *SyncMap[TKey, TData]) Get(key TKey) (TData, bool) {
s.lock.Lock() s.lock.Lock()
defer s.lock.Unlock() defer s.lock.Unlock()
@@ -50,6 +67,39 @@ func (s *SyncMap[TKey, TData]) Get(key TKey) (TData, bool) {
} }
} }
func (s *SyncMap[TKey, TData]) GetAndSetIfNotContains(key TKey, data TData) TData {
s.lock.Lock()
defer s.lock.Unlock()
if s.data == nil {
s.data = make(map[TKey]TData)
}
if v, ok := s.data[key]; ok {
return v
} else {
s.data[key] = data
return data
}
}
func (s *SyncMap[TKey, TData]) GetAndSetIfNotContainsFunc(key TKey, data func() TData) TData {
s.lock.Lock()
defer s.lock.Unlock()
if s.data == nil {
s.data = make(map[TKey]TData)
}
if v, ok := s.data[key]; ok {
return v
} else {
dataObj := data()
s.data[key] = dataObj
return dataObj
}
}
func (s *SyncMap[TKey, TData]) Delete(key TKey) bool { func (s *SyncMap[TKey, TData]) Delete(key TKey) bool {
s.lock.Lock() s.lock.Lock()
defer s.lock.Unlock() defer s.lock.Unlock()

View File

@@ -493,11 +493,12 @@ func (b *Builder) Format(level LogPrintLevel) string {
// If the error is SevErr we also send it to the error-service // If the error is SevErr we also send it to the error-service
func (b *Builder) Fatal(ctxs ...context.Context) { func (b *Builder) Fatal(ctxs ...context.Context) {
b.errorData.Severity = SevFatal
for _, dctx := range ctxs { for _, dctx := range ctxs {
b.CtxData(MethodFatal, dctx) b.CtxData(MethodFatal, dctx)
} }
b.errorData.Severity = SevFatal
b.errorData.Log(stackSkipLogger.WithLevel(zerolog.FatalLevel)) b.errorData.Log(stackSkipLogger.WithLevel(zerolog.FatalLevel))
b.errorData.CallListener(MethodFatal) b.errorData.CallListener(MethodFatal)

View File

@@ -56,7 +56,7 @@ func wrapExErr(e *ExErr, msg string, cat ErrorCategory, stacktraceskip int) *ExE
UniqueID: newID(), UniqueID: newID(),
Category: cat, Category: cat,
Type: TypeWrap, Type: TypeWrap,
Severity: SevErr, Severity: e.Severity,
Timestamp: time.Now(), Timestamp: time.Now(),
StatusCode: e.StatusCode, StatusCode: e.StatusCode,
Message: msg, Message: msg,

View File

@@ -1,91 +1,14 @@
package exerr package exerr
import ( type Method string
"gogs.mikescher.com/BlackForestBytes/goext/dataext"
"gogs.mikescher.com/BlackForestBytes/goext/langext" const (
MethodOutput Method = "OUTPUT"
MethodPrint Method = "PRINT"
MethodBuild Method = "BUILD"
MethodFatal Method = "FATAL"
) )
type ErrorCategory struct{ Category string }
var (
CatWrap = ErrorCategory{"Wrap"} // The error is simply wrapping another error (e.g. when a grpc call returns an error)
CatSystem = ErrorCategory{"System"} // An internal system error (e.g. connection to db failed)
CatUser = ErrorCategory{"User"} // The user (the API caller) did something wrong (e.g. he has no permissions to do this)
CatForeign = ErrorCategory{"Foreign"} // A foreign error that some component threw (e.g. an unknown mongodb error), happens if we call Wrap(..) on an non-bmerror value
)
//goland:noinspection GoUnusedGlobalVariable
var AllCategories = []ErrorCategory{CatWrap, CatSystem, CatUser, CatForeign}
type ErrorSeverity struct{ Severity string }
var (
SevTrace = ErrorSeverity{"Trace"}
SevDebug = ErrorSeverity{"Debug"}
SevInfo = ErrorSeverity{"Info"}
SevWarn = ErrorSeverity{"Warn"}
SevErr = ErrorSeverity{"Err"}
SevFatal = ErrorSeverity{"Fatal"}
)
//goland:noinspection GoUnusedGlobalVariable
var AllSeverities = []ErrorSeverity{SevTrace, SevDebug, SevInfo, SevWarn, SevErr, SevFatal}
type ErrorType struct {
Key string
DefaultStatusCode *int
}
//goland:noinspection GoUnusedGlobalVariable
var (
TypeInternal = NewType("INTERNAL_ERROR", langext.Ptr(500))
TypePanic = NewType("PANIC", langext.Ptr(500))
TypeNotImplemented = NewType("NOT_IMPLEMENTED", langext.Ptr(500))
TypeMongoQuery = NewType("MONGO_QUERY", langext.Ptr(500))
TypeCursorTokenDecode = NewType("CURSOR_TOKEN_DECODE", langext.Ptr(500))
TypeMongoFilter = NewType("MONGO_FILTER", langext.Ptr(500))
TypeMongoReflection = NewType("MONGO_REFLECTION", langext.Ptr(500))
TypeMongoInvalidOpt = NewType("MONGO_INVALIDOPT", langext.Ptr(500))
TypeSQLQuery = NewType("SQL_QUERY", langext.Ptr(500))
TypeSQLBuild = NewType("SQL_BUILD", langext.Ptr(500))
TypeSQLDecode = NewType("SQL_DECODE", langext.Ptr(500))
TypeWrap = NewType("Wrap", nil)
TypeBindFailURI = NewType("BINDFAIL_URI", langext.Ptr(400))
TypeBindFailQuery = NewType("BINDFAIL_QUERY", langext.Ptr(400))
TypeBindFailJSON = NewType("BINDFAIL_JSON", langext.Ptr(400))
TypeBindFailFormData = NewType("BINDFAIL_FORMDATA", langext.Ptr(400))
TypeBindFailHeader = NewType("BINDFAIL_HEADER", langext.Ptr(400))
TypeMarshalEntityID = NewType("MARSHAL_ENTITY_ID", langext.Ptr(400))
TypeInvalidCSID = NewType("INVALID_CSID", langext.Ptr(400))
TypeGoogleStatuscode = NewType("GOOGLE_STATUSCODE", langext.Ptr(400))
TypeGoogleResponse = NewType("GOOGLE_RESPONSE", langext.Ptr(400))
TypeUnauthorized = NewType("UNAUTHORIZED", langext.Ptr(401))
TypeAuthFailed = NewType("AUTH_FAILED", langext.Ptr(401))
// other values come from the downstream application that uses goext
)
var registeredTypes = dataext.SyncMap[string, ErrorType]{}
func NewType(key string, defStatusCode *int) ErrorType {
et := ErrorType{key, defStatusCode}
registeredTypes.Set(key, et)
return et
}
func ListRegisteredTypes() []ErrorType {
return registeredTypes.GetAllValues()
}
type LogPrintLevel string type LogPrintLevel string
const ( const (

89
exerr/dataCategory.go Normal file
View File

@@ -0,0 +1,89 @@
package exerr
import (
"encoding/json"
"errors"
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsontype"
"reflect"
)
type ErrorCategory struct{ Category string }
var (
CatWrap = ErrorCategory{"Wrap"} // The error is simply wrapping another error (e.g. when a grpc call returns an error)
CatSystem = ErrorCategory{"System"} // An internal system error (e.g. connection to db failed)
CatUser = ErrorCategory{"User"} // The user (the API caller) did something wrong (e.g. he has no permissions to do this)
CatForeign = ErrorCategory{"Foreign"} // A foreign error that some component threw (e.g. an unknown mongodb error), happens if we call Wrap(..) on an non-bmerror value
)
func (e *ErrorCategory) UnmarshalJSON(bytes []byte) error {
return json.Unmarshal(bytes, &e.Category)
}
func (e ErrorCategory) MarshalJSON() ([]byte, error) {
return json.Marshal(e.Category)
}
func (e *ErrorCategory) UnmarshalBSONValue(bt bsontype.Type, data []byte) error {
if bt == bson.TypeNull {
// we can't set nil in UnmarshalBSONValue (so we use default(struct))
// Use mongoext.CreateGoExtBsonRegistry if you need to unmarsh pointer values
// https://stackoverflow.com/questions/75167597
// https://jira.mongodb.org/browse/GODRIVER-2252
*e = ErrorCategory{}
return nil
}
if bt != bson.TypeString {
return errors.New(fmt.Sprintf("cannot unmarshal %v into String", bt))
}
var tt string
err := bson.RawValue{Type: bt, Value: data}.Unmarshal(&tt)
if err != nil {
return err
}
*e = ErrorCategory{tt}
return nil
}
func (e ErrorCategory) MarshalBSONValue() (bsontype.Type, []byte, error) {
return bson.MarshalValue(e.Category)
}
func (e ErrorCategory) DecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
if val.Kind() == reflect.Ptr && val.IsNil() {
if !val.CanSet() {
return errors.New("ValueUnmarshalerDecodeValue")
}
val.Set(reflect.New(val.Type().Elem()))
}
tp, src, err := bsonrw.Copier{}.CopyValueToBytes(vr)
if err != nil {
return err
}
if val.Kind() == reflect.Ptr && len(src) == 0 {
val.Set(reflect.Zero(val.Type()))
return nil
}
err = e.UnmarshalBSONValue(tp, src)
if err != nil {
return err
}
if val.Kind() == reflect.Ptr {
val.Set(reflect.ValueOf(&e))
} else {
val.Set(reflect.ValueOf(e))
}
return nil
}
//goland:noinspection GoUnusedGlobalVariable
var AllCategories = []ErrorCategory{CatWrap, CatSystem, CatUser, CatForeign}

91
exerr/dataSeverity.go Normal file
View File

@@ -0,0 +1,91 @@
package exerr
import (
"encoding/json"
"errors"
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsontype"
"reflect"
)
type ErrorSeverity struct{ Severity string }
var (
SevTrace = ErrorSeverity{"Trace"}
SevDebug = ErrorSeverity{"Debug"}
SevInfo = ErrorSeverity{"Info"}
SevWarn = ErrorSeverity{"Warn"}
SevErr = ErrorSeverity{"Err"}
SevFatal = ErrorSeverity{"Fatal"}
)
func (e *ErrorSeverity) UnmarshalJSON(bytes []byte) error {
return json.Unmarshal(bytes, &e.Severity)
}
func (e ErrorSeverity) MarshalJSON() ([]byte, error) {
return json.Marshal(e.Severity)
}
func (e *ErrorSeverity) UnmarshalBSONValue(bt bsontype.Type, data []byte) error {
if bt == bson.TypeNull {
// we can't set nil in UnmarshalBSONValue (so we use default(struct))
// Use mongoext.CreateGoExtBsonRegistry if you need to unmarsh pointer values
// https://stackoverflow.com/questions/75167597
// https://jira.mongodb.org/browse/GODRIVER-2252
*e = ErrorSeverity{}
return nil
}
if bt != bson.TypeString {
return errors.New(fmt.Sprintf("cannot unmarshal %v into String", bt))
}
var tt string
err := bson.RawValue{Type: bt, Value: data}.Unmarshal(&tt)
if err != nil {
return err
}
*e = ErrorSeverity{tt}
return nil
}
func (e ErrorSeverity) MarshalBSONValue() (bsontype.Type, []byte, error) {
return bson.MarshalValue(e.Severity)
}
func (e ErrorSeverity) DecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
if val.Kind() == reflect.Ptr && val.IsNil() {
if !val.CanSet() {
return errors.New("ValueUnmarshalerDecodeValue")
}
val.Set(reflect.New(val.Type().Elem()))
}
tp, src, err := bsonrw.Copier{}.CopyValueToBytes(vr)
if err != nil {
return err
}
if val.Kind() == reflect.Ptr && len(src) == 0 {
val.Set(reflect.Zero(val.Type()))
return nil
}
err = e.UnmarshalBSONValue(tp, src)
if err != nil {
return err
}
if val.Kind() == reflect.Ptr {
val.Set(reflect.ValueOf(&e))
} else {
val.Set(reflect.ValueOf(e))
}
return nil
}
//goland:noinspection GoUnusedGlobalVariable
var AllSeverities = []ErrorSeverity{SevTrace, SevDebug, SevInfo, SevWarn, SevErr, SevFatal}

155
exerr/dataType.go Normal file
View File

@@ -0,0 +1,155 @@
package exerr
import (
"encoding/json"
"errors"
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsontype"
"gogs.mikescher.com/BlackForestBytes/goext/dataext"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"reflect"
)
type ErrorType struct {
Key string
DefaultStatusCode *int
}
//goland:noinspection GoUnusedGlobalVariable
var (
TypeInternal = NewType("INTERNAL_ERROR", langext.Ptr(500))
TypePanic = NewType("PANIC", langext.Ptr(500))
TypeNotImplemented = NewType("NOT_IMPLEMENTED", langext.Ptr(500))
TypeMongoQuery = NewType("MONGO_QUERY", langext.Ptr(500))
TypeCursorTokenDecode = NewType("CURSOR_TOKEN_DECODE", langext.Ptr(500))
TypeMongoFilter = NewType("MONGO_FILTER", langext.Ptr(500))
TypeMongoReflection = NewType("MONGO_REFLECTION", langext.Ptr(500))
TypeMongoInvalidOpt = NewType("MONGO_INVALIDOPT", langext.Ptr(500))
TypeSQLQuery = NewType("SQL_QUERY", langext.Ptr(500))
TypeSQLBuild = NewType("SQL_BUILD", langext.Ptr(500))
TypeSQLDecode = NewType("SQL_DECODE", langext.Ptr(500))
TypeWrap = NewType("Wrap", nil)
TypeBindFailURI = NewType("BINDFAIL_URI", langext.Ptr(400))
TypeBindFailQuery = NewType("BINDFAIL_QUERY", langext.Ptr(400))
TypeBindFailJSON = NewType("BINDFAIL_JSON", langext.Ptr(400))
TypeBindFailFormData = NewType("BINDFAIL_FORMDATA", langext.Ptr(400))
TypeBindFailHeader = NewType("BINDFAIL_HEADER", langext.Ptr(400))
TypeMarshalEntityID = NewType("MARSHAL_ENTITY_ID", langext.Ptr(400))
TypeInvalidCSID = NewType("INVALID_CSID", langext.Ptr(400))
TypeGoogleStatuscode = NewType("GOOGLE_STATUSCODE", langext.Ptr(400))
TypeGoogleResponse = NewType("GOOGLE_RESPONSE", langext.Ptr(400))
TypeUnauthorized = NewType("UNAUTHORIZED", langext.Ptr(401))
TypeAuthFailed = NewType("AUTH_FAILED", langext.Ptr(401))
TypeInvalidImage = NewType("IMAGEEXT_INVALID_IMAGE", langext.Ptr(400))
TypeInvalidMimeType = NewType("IMAGEEXT_INVALID_MIMETYPE", langext.Ptr(400))
// other values come from the downstream application that uses goext
)
func (e *ErrorType) UnmarshalJSON(bytes []byte) error {
var k string
err := json.Unmarshal(bytes, &k)
if err != nil {
return err
}
if d, ok := registeredTypes.Get(k); ok {
*e = d
return nil
} else {
*e = ErrorType{k, nil}
return nil
}
}
func (e ErrorType) MarshalJSON() ([]byte, error) {
return json.Marshal(e.Key)
}
func (e *ErrorType) UnmarshalBSONValue(bt bsontype.Type, data []byte) error {
if bt == bson.TypeNull {
// we can't set nil in UnmarshalBSONValue (so we use default(struct))
// Use mongoext.CreateGoExtBsonRegistry if you need to unmarsh pointer values
// https://stackoverflow.com/questions/75167597
// https://jira.mongodb.org/browse/GODRIVER-2252
*e = ErrorType{}
return nil
}
if bt != bson.TypeString {
return errors.New(fmt.Sprintf("cannot unmarshal %v into String", bt))
}
var tt string
err := bson.RawValue{Type: bt, Value: data}.Unmarshal(&tt)
if err != nil {
return err
}
if d, ok := registeredTypes.Get(tt); ok {
*e = d
return nil
} else {
*e = ErrorType{tt, nil}
return nil
}
}
func (e ErrorType) MarshalBSONValue() (bsontype.Type, []byte, error) {
return bson.MarshalValue(e.Key)
}
func (e ErrorType) DecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
if val.Kind() == reflect.Ptr && val.IsNil() {
if !val.CanSet() {
return errors.New("ValueUnmarshalerDecodeValue")
}
val.Set(reflect.New(val.Type().Elem()))
}
tp, src, err := bsonrw.Copier{}.CopyValueToBytes(vr)
if err != nil {
return err
}
if val.Kind() == reflect.Ptr && len(src) == 0 {
val.Set(reflect.Zero(val.Type()))
return nil
}
err = e.UnmarshalBSONValue(tp, src)
if err != nil {
return err
}
if val.Kind() == reflect.Ptr {
val.Set(reflect.ValueOf(&e))
} else {
val.Set(reflect.ValueOf(e))
}
return nil
}
var registeredTypes = dataext.SyncMap[string, ErrorType]{}
func NewType(key string, defStatusCode *int) ErrorType {
et := ErrorType{key, defStatusCode}
registeredTypes.Set(key, et)
return et
}
func ListRegisteredTypes() []ErrorType {
return registeredTypes.GetAllValues()
}

153
exerr/data_test.go Normal file
View File

@@ -0,0 +1,153 @@
package exerr
import (
"context"
"encoding/json"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"gogs.mikescher.com/BlackForestBytes/goext/tst"
"testing"
"time"
)
func TestJSONMarshalErrorCategory(t *testing.T) {
c1 := CatSystem
jsonbin := tst.Must(json.Marshal(c1))(t)
var c2 ErrorCategory
tst.AssertNoErr(t, json.Unmarshal(jsonbin, &c2))
tst.AssertEqual(t, c1, c2)
tst.AssertEqual(t, string(jsonbin), "\"System\"")
}
func TestJSONMarshalErrorSeverity(t *testing.T) {
c1 := SevErr
jsonbin := tst.Must(json.Marshal(c1))(t)
var c2 ErrorSeverity
tst.AssertNoErr(t, json.Unmarshal(jsonbin, &c2))
tst.AssertEqual(t, c1, c2)
tst.AssertEqual(t, string(jsonbin), "\"Err\"")
}
func TestJSONMarshalErrorType(t *testing.T) {
c1 := TypeNotImplemented
jsonbin := tst.Must(json.Marshal(c1))(t)
var c2 ErrorType
tst.AssertNoErr(t, json.Unmarshal(jsonbin, &c2))
tst.AssertEqual(t, c1, c2)
tst.AssertEqual(t, string(jsonbin), "\"NOT_IMPLEMENTED\"")
}
func TestBSONMarshalErrorCategory(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 350*time.Millisecond)
defer cancel()
client, err := mongo.Connect(ctx)
if err != nil {
t.Skip("Skip test - no local mongo found")
return
}
err = client.Ping(ctx, nil)
if err != nil {
t.Skip("Skip test - no local mongo found")
return
}
primimd := primitive.NewObjectID()
_, err = client.Database("_test").Collection("goext-cicd").InsertOne(ctx, bson.M{"_id": primimd, "val": CatSystem})
tst.AssertNoErr(t, err)
cursor := client.Database("_test").Collection("goext-cicd").FindOne(ctx, bson.M{"_id": primimd, "val": bson.M{"$type": "string"}})
var c1 struct {
ID primitive.ObjectID `bson:"_id"`
Val ErrorCategory `bson:"val"`
}
err = cursor.Decode(&c1)
tst.AssertNoErr(t, err)
tst.AssertEqual(t, c1.Val, CatSystem)
}
func TestBSONMarshalErrorSeverity(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 350*time.Millisecond)
defer cancel()
client, err := mongo.Connect(ctx)
if err != nil {
t.Skip("Skip test - no local mongo found")
return
}
err = client.Ping(ctx, nil)
if err != nil {
t.Skip("Skip test - no local mongo found")
return
}
primimd := primitive.NewObjectID()
_, err = client.Database("_test").Collection("goext-cicd").InsertOne(ctx, bson.M{"_id": primimd, "val": SevErr})
tst.AssertNoErr(t, err)
cursor := client.Database("_test").Collection("goext-cicd").FindOne(ctx, bson.M{"_id": primimd, "val": bson.M{"$type": "string"}})
var c1 struct {
ID primitive.ObjectID `bson:"_id"`
Val ErrorSeverity `bson:"val"`
}
err = cursor.Decode(&c1)
tst.AssertNoErr(t, err)
tst.AssertEqual(t, c1.Val, SevErr)
}
func TestBSONMarshalErrorType(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 350*time.Millisecond)
defer cancel()
client, err := mongo.Connect(ctx)
if err != nil {
t.Skip("Skip test - no local mongo found")
return
}
err = client.Ping(ctx, nil)
if err != nil {
t.Skip("Skip test - no local mongo found")
return
}
primimd := primitive.NewObjectID()
_, err = client.Database("_test").Collection("goext-cicd").InsertOne(ctx, bson.M{"_id": primimd, "val": TypeNotImplemented})
tst.AssertNoErr(t, err)
cursor := client.Database("_test").Collection("goext-cicd").FindOne(ctx, bson.M{"_id": primimd, "val": bson.M{"$type": "string"}})
var c1 struct {
ID primitive.ObjectID `bson:"_id"`
Val ErrorType `bson:"val"`
}
err = cursor.Decode(&c1)
tst.AssertNoErr(t, err)
tst.AssertEqual(t, c1.Val, TypeNotImplemented)
}

View File

@@ -84,6 +84,9 @@ func (ee *ExErr) FormatLog(lvl LogPrintLevel) string {
if lvl == LogPrintShort { if lvl == LogPrintShort {
msg := ee.Message msg := ee.Message
if msg == "" {
msg = ee.RecursiveMessage()
}
if ee.OriginalError != nil && ee.OriginalError.Category == CatForeign { if ee.OriginalError != nil && ee.OriginalError.Category == CatForeign {
msg = msg + " (" + strings.ReplaceAll(ee.OriginalError.Message, "\n", " ") + ")" msg = msg + " (" + strings.ReplaceAll(ee.OriginalError.Message, "\n", " ") + ")"
} }

View File

@@ -2,10 +2,19 @@ package exerr
import ( import (
"errors" "errors"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"gogs.mikescher.com/BlackForestBytes/goext/tst" "gogs.mikescher.com/BlackForestBytes/goext/tst"
"os"
"testing" "testing"
) )
func TestMain(m *testing.M) {
if !Initialized() {
Init(ErrorPackageConfigInit{ZeroLogErrTraces: langext.PFalse, ZeroLogAllTraces: langext.PFalse})
}
os.Exit(m.Run())
}
type golangErr struct { type golangErr struct {
Message string Message string
} }

View File

@@ -4,15 +4,6 @@ import (
"sync" "sync"
) )
type Method string
const (
MethodOutput Method = "OUTPUT"
MethodPrint Method = "PRINT"
MethodBuild Method = "BUILD"
MethodFatal Method = "FATAL"
)
type Listener = func(method Method, v *ExErr) type Listener = func(method Method, v *ExErr)
var listenerLock = sync.Mutex{} var listenerLock = sync.Mutex{}

View File

@@ -72,20 +72,22 @@ func NewEngine(opt Options) *GinWrapper {
engine.Use(CorsMiddleware()) engine.Use(CorsMiddleware())
} }
// do not debug-print routes if wrapper.ginDebug {
gin.DebugPrintRouteFunc = func(_, _, _ string, _ int) {} gin.SetMode(gin.DebugMode)
if !wrapper.ginDebug { // do not debug-print routes
gin.SetMode(gin.ReleaseMode) gin.DebugPrintRouteFunc = func(_, _, _ string, _ int) {}
if !wrapper.suppressGinLogs { if !wrapper.suppressGinLogs {
ginlogger := gin.Logger() ginlogger := gin.Logger()
engine.Use(func(context *gin.Context) { engine.Use(func(context *gin.Context) { ginlogger(context) })
ginlogger(context)
})
} }
} else { } else {
gin.SetMode(gin.DebugMode) gin.SetMode(gin.ReleaseMode)
// do not debug-print routes
gin.DebugPrintRouteFunc = func(_, _, _ string, _ int) {}
} }
return wrapper return wrapper

View File

@@ -1,13 +1,8 @@
package ginext package ginext
import ( import (
"context"
"fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gogs.mikescher.com/BlackForestBytes/goext/exerr" "gogs.mikescher.com/BlackForestBytes/goext/exerr"
json "gogs.mikescher.com/BlackForestBytes/goext/gojson"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"os"
) )
type cookieval struct { type cookieval struct {
@@ -41,464 +36,6 @@ type InspectableHTTPResponse interface {
Headers() []string 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 { func NotImplemented() HTTPResponse {
return Error(exerr.New(exerr.TypeNotImplemented, "").Build()) return Error(exerr.New(exerr.TypeNotImplemented, "").Build())
} }

58
ginext/responseData.go Normal file
View File

@@ -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}
}

View File

@@ -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}
}

56
ginext/responseEmpty.go Normal file
View File

@@ -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}
}

73
ginext/responseFile.go Normal file
View File

@@ -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}
}

70
ginext/responseJson.go Normal file
View File

@@ -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}
}

77
ginext/responseJsonAPI.go Normal file
View File

@@ -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()),
}
}

View File

@@ -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}
}

View File

@@ -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}
}

57
ginext/responseText.go Normal file
View File

@@ -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}
}

36
go.mod
View File

@@ -3,36 +3,42 @@ module gogs.mikescher.com/BlackForestBytes/goext
go 1.22 go 1.22
require ( require (
github.com/gin-gonic/gin v1.9.1 github.com/gin-gonic/gin v1.10.0
github.com/glebarez/go-sqlite v1.22.0 // only needed for tests -.- github.com/glebarez/go-sqlite v1.22.0 // only needed for tests -.-
github.com/jmoiron/sqlx v1.4.0 github.com/jmoiron/sqlx v1.4.0
github.com/rs/xid v1.5.0 github.com/rs/xid v1.5.0
github.com/rs/zerolog v1.32.0 github.com/rs/zerolog v1.33.0
go.mongodb.org/mongo-driver v1.15.0 go.mongodb.org/mongo-driver v1.15.0
golang.org/x/crypto v0.22.0 golang.org/x/crypto v0.24.0
golang.org/x/sys v0.19.0 golang.org/x/sys v0.21.0
golang.org/x/term v0.19.0 golang.org/x/term v0.21.0
) )
require ( require (
github.com/bytedance/sonic v1.11.6 // indirect github.com/disintegration/imaging v1.6.2
github.com/jung-kurt/gofpdf v1.16.2
golang.org/x/sync v0.7.0
)
require (
github.com/bytedance/sonic v1.11.8 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect github.com/cloudwego/iasm v0.2.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gabriel-vasile/mimetype v1.4.4 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/go-playground/validator/v10 v10.21.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.3 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.5.0 // indirect github.com/google/uuid v1.5.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.8 // 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/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
@@ -47,11 +53,11 @@ require (
github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240424034433-3c2c7870ae76 // indirect github.com/youmark/pkcs8 v0.0.0-20240424034433-3c2c7870ae76 // indirect
golang.org/x/arch v0.7.0 // indirect golang.org/x/arch v0.8.0 // indirect
golang.org/x/net v0.24.0 // indirect golang.org/x/image v0.17.0 // indirect
golang.org/x/sync v0.7.0 // indirect golang.org/x/net v0.26.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.16.0 // indirect
google.golang.org/protobuf v1.34.0 // indirect google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.37.6 // indirect modernc.org/libc v1.37.6 // indirect
modernc.org/mathutil v1.6.0 // indirect modernc.org/mathutil v1.6.0 // indirect

59
go.sum
View File

@@ -1,4 +1,5 @@
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
@@ -17,6 +18,10 @@ github.com/bytedance/sonic v1.11.5 h1:G00FYjjqll5iQ1PYXynbg/hyzqBqavH8Mo9/oTopd9
github.com/bytedance/sonic v1.11.5/go.mod h1:X2PC2giUdj/Cv2lliWFLk6c/DUQok5rViJSemeB0wDw= github.com/bytedance/sonic v1.11.5/go.mod h1:X2PC2giUdj/Cv2lliWFLk6c/DUQok5rViJSemeB0wDw=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic v1.11.7 h1:k/l9p1hZpNIMJSk37wL9ltkcpqLfIho1vYthi4xT2t4=
github.com/bytedance/sonic v1.11.7/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic v1.11.8 h1:Zw/j1KfiS+OYTi9lyB3bb0CFxPJVkM17k1wyDG32LRA=
github.com/bytedance/sonic v1.11.8/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.0/go.mod h1:UmRT+IRTGKz/DAkzcEGzyVqQFJ7H9BqwBO3pm9H/+HY= github.com/bytedance/sonic/loader v0.1.0/go.mod h1:UmRT+IRTGKz/DAkzcEGzyVqQFJ7H9BqwBO3pm9H/+HY=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
@@ -42,14 +47,20 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@@ -68,12 +79,16 @@ github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.21.0 h1:4fZA11ovvtkdgaeev9RGWPgc1uj3H8W+rNYyH/ySBb0=
github.com/go-playground/validator/v10 v10.21.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@@ -93,6 +108,9 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/jung-kurt/gofpdf v1.16.2 h1:jgbatWHfRlPYiK85qgevsZTHviWXKwB1TTiKdz5PtRc=
github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/YmFV8S2vmK0=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
@@ -107,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.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 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 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/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 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
@@ -141,6 +161,8 @@ github.com/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtos
github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -152,10 +174,14 @@ github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -191,6 +217,8 @@ go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGc
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
@@ -207,6 +235,17 @@ golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= 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.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/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -223,6 +262,10 @@ golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= 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.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-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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
@@ -248,6 +291,10 @@ golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= 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.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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
@@ -258,6 +305,10 @@ golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= 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.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.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -266,6 +317,10 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 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.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-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.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
@@ -279,6 +334,10 @@ google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGm
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4= google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4=
google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -1,5 +1,5 @@
package goext package goext
const GoextVersion = "0.0.445" const GoextVersion = "0.0.471"
const GoextVersionTimestamp = "2024-05-03T15:28:53+0200" const GoextVersionTimestamp = "2024-06-11T19:39:43+0200"

3
imageext/enums.go Normal file
View File

@@ -0,0 +1,3 @@
package imageext
//go:generate go run ../_gen/enum-generate.go -- enums_gen.go

216
imageext/enums_gen.go Normal file
View File

@@ -0,0 +1,216 @@
// Code generated by enum-generate.go DO NOT EDIT.
package imageext
import "gogs.mikescher.com/BlackForestBytes/goext/langext"
import "gogs.mikescher.com/BlackForestBytes/goext/enums"
const ChecksumEnumGenerator = "1da5383c33ee442fd0b899369053f66bdc85bed2dbf906949d3edfeedfe13340" // GoExtVersion: 0.0.449
// ================================ ImageFit ================================
//
// File: image.go
// StringEnum: true
// DescrEnum: false
// DataEnum: false
//
var __ImageFitValues = []ImageFit{
ImageFitStretch,
ImageFitCover,
ImageFitContainCenter,
ImageFitContainTopLeft,
ImageFitContainTopRight,
ImageFitContainBottomLeft,
ImageFitContainBottomRight,
}
var __ImageFitVarnames = map[ImageFit]string{
ImageFitStretch: "ImageFitStretch",
ImageFitCover: "ImageFitCover",
ImageFitContainCenter: "ImageFitContainCenter",
ImageFitContainTopLeft: "ImageFitContainTopLeft",
ImageFitContainTopRight: "ImageFitContainTopRight",
ImageFitContainBottomLeft: "ImageFitContainBottomLeft",
ImageFitContainBottomRight: "ImageFitContainBottomRight",
}
func (e ImageFit) Valid() bool {
return langext.InArray(e, __ImageFitValues)
}
func (e ImageFit) Values() []ImageFit {
return __ImageFitValues
}
func (e ImageFit) ValuesAny() []any {
return langext.ArrCastToAny(__ImageFitValues)
}
func (e ImageFit) ValuesMeta() []enums.EnumMetaValue {
return ImageFitValuesMeta()
}
func (e ImageFit) String() string {
return string(e)
}
func (e ImageFit) VarName() string {
if d, ok := __ImageFitVarnames[e]; ok {
return d
}
return ""
}
func (e ImageFit) TypeName() string {
return "ImageFit"
}
func (e ImageFit) PackageName() string {
return "media"
}
func (e ImageFit) Meta() enums.EnumMetaValue {
return enums.EnumMetaValue{VarName: e.VarName(), Value: e, Description: nil}
}
func ParseImageFit(vv string) (ImageFit, bool) {
for _, ev := range __ImageFitValues {
if string(ev) == vv {
return ev, true
}
}
return "", false
}
func ImageFitValues() []ImageFit {
return __ImageFitValues
}
func ImageFitValuesMeta() []enums.EnumMetaValue {
return []enums.EnumMetaValue{
ImageFitStretch.Meta(),
ImageFitCover.Meta(),
ImageFitContainCenter.Meta(),
ImageFitContainTopLeft.Meta(),
ImageFitContainTopRight.Meta(),
ImageFitContainBottomLeft.Meta(),
ImageFitContainBottomRight.Meta(),
}
}
// ================================ ImageCompresson ================================
//
// File: image.go
// StringEnum: true
// DescrEnum: false
// DataEnum: false
//
var __ImageCompressonValues = []ImageCompresson{
CompressionPNGNone,
CompressionPNGSpeed,
CompressionPNGBest,
CompressionJPEG100,
CompressionJPEG90,
CompressionJPEG80,
CompressionJPEG70,
CompressionJPEG60,
CompressionJPEG50,
CompressionJPEG25,
CompressionJPEG10,
CompressionJPEG1,
}
var __ImageCompressonVarnames = map[ImageCompresson]string{
CompressionPNGNone: "CompressionPNGNone",
CompressionPNGSpeed: "CompressionPNGSpeed",
CompressionPNGBest: "CompressionPNGBest",
CompressionJPEG100: "CompressionJPEG100",
CompressionJPEG90: "CompressionJPEG90",
CompressionJPEG80: "CompressionJPEG80",
CompressionJPEG70: "CompressionJPEG70",
CompressionJPEG60: "CompressionJPEG60",
CompressionJPEG50: "CompressionJPEG50",
CompressionJPEG25: "CompressionJPEG25",
CompressionJPEG10: "CompressionJPEG10",
CompressionJPEG1: "CompressionJPEG1",
}
func (e ImageCompresson) Valid() bool {
return langext.InArray(e, __ImageCompressonValues)
}
func (e ImageCompresson) Values() []ImageCompresson {
return __ImageCompressonValues
}
func (e ImageCompresson) ValuesAny() []any {
return langext.ArrCastToAny(__ImageCompressonValues)
}
func (e ImageCompresson) ValuesMeta() []enums.EnumMetaValue {
return ImageCompressonValuesMeta()
}
func (e ImageCompresson) String() string {
return string(e)
}
func (e ImageCompresson) VarName() string {
if d, ok := __ImageCompressonVarnames[e]; ok {
return d
}
return ""
}
func (e ImageCompresson) TypeName() string {
return "ImageCompresson"
}
func (e ImageCompresson) PackageName() string {
return "media"
}
func (e ImageCompresson) Meta() enums.EnumMetaValue {
return enums.EnumMetaValue{VarName: e.VarName(), Value: e, Description: nil}
}
func ParseImageCompresson(vv string) (ImageCompresson, bool) {
for _, ev := range __ImageCompressonValues {
if string(ev) == vv {
return ev, true
}
}
return "", false
}
func ImageCompressonValues() []ImageCompresson {
return __ImageCompressonValues
}
func ImageCompressonValuesMeta() []enums.EnumMetaValue {
return []enums.EnumMetaValue{
CompressionPNGNone.Meta(),
CompressionPNGSpeed.Meta(),
CompressionPNGBest.Meta(),
CompressionJPEG100.Meta(),
CompressionJPEG90.Meta(),
CompressionJPEG80.Meta(),
CompressionJPEG70.Meta(),
CompressionJPEG60.Meta(),
CompressionJPEG50.Meta(),
CompressionJPEG25.Meta(),
CompressionJPEG10.Meta(),
CompressionJPEG1.Meta(),
}
}
// ================================ ================= ================================
func AllPackageEnums() []enums.Enum {
return []enums.Enum{
ImageFitStretch, // ImageFit
CompressionPNGNone, // ImageCompresson
}
}

321
imageext/image.go Normal file
View File

@@ -0,0 +1,321 @@
package imageext
import (
"bytes"
"fmt"
"github.com/disintegration/imaging"
"gogs.mikescher.com/BlackForestBytes/goext/exerr"
"gogs.mikescher.com/BlackForestBytes/goext/mathext"
"image"
"image/color"
"image/draw"
"image/jpeg"
"image/png"
"io"
"math"
)
type ImageFit string //@enum:type
const (
ImageFitStretch ImageFit = "STRETCH"
ImageFitCover ImageFit = "COVER"
ImageFitContainCenter ImageFit = "CONTAIN_CENTER"
ImageFitContainTopLeft ImageFit = "CONTAIN_TOPLEFT"
ImageFitContainTopRight ImageFit = "CONTAIN_TOPRIGHT"
ImageFitContainBottomLeft ImageFit = "CONTAIN_BOTTOMLEFT"
ImageFitContainBottomRight ImageFit = "CONTAIN_BOTTOMRIGHT"
)
type ImageCrop struct { // all crop values are percentages!
CropX float64 `bson:"cropX" json:"cropX"`
CropY float64 `bson:"cropY" json:"cropY"`
CropWidth float64 `bson:"cropWidth" json:"cropWidth"`
CropHeight float64 `bson:"cropHeight" json:"cropHeight"`
}
type ImageCompresson string //@enum:type
const (
CompressionPNGNone ImageCompresson = "PNG_NONE"
CompressionPNGSpeed ImageCompresson = "PNG_SPEED"
CompressionPNGBest ImageCompresson = "PNG_BEST"
CompressionJPEG100 ImageCompresson = "JPEG_100"
CompressionJPEG90 ImageCompresson = "JPEG_090"
CompressionJPEG80 ImageCompresson = "JPEG_080"
CompressionJPEG70 ImageCompresson = "JPEG_070"
CompressionJPEG60 ImageCompresson = "JPEG_060"
CompressionJPEG50 ImageCompresson = "JPEG_050"
CompressionJPEG25 ImageCompresson = "JPEG_025"
CompressionJPEG10 ImageCompresson = "JPEG_010"
CompressionJPEG1 ImageCompresson = "JPEG_001"
)
func CropImage(img image.Image, px float64, py float64, pw float64, ph float64) (image.Image, error) {
type subImager interface {
SubImage(r image.Rectangle) image.Image
}
x := int(float64(img.Bounds().Dx()) * px)
y := int(float64(img.Bounds().Dy()) * py)
w := int(float64(img.Bounds().Dx()) * pw)
h := int(float64(img.Bounds().Dy()) * ph)
if simg, ok := img.(subImager); ok {
return simg.SubImage(image.Rect(x, y, x+w, y+h)), nil
} else {
bfr1 := bytes.Buffer{}
err := png.Encode(&bfr1, img)
if err != nil {
return nil, exerr.Wrap(err, "").Build()
}
imgPNG, err := png.Decode(&bfr1)
if err != nil {
return nil, exerr.Wrap(err, "").Build()
}
return imgPNG.(subImager).SubImage(image.Rect(x, y, w+w, y+h)), nil
}
}
func EncodeImage(img image.Image, compression ImageCompresson) (bytes.Buffer, string, error) {
var err error
bfr := bytes.Buffer{}
switch compression {
case CompressionPNGNone:
enc := &png.Encoder{CompressionLevel: png.NoCompression}
err = enc.Encode(&bfr, img)
if err != nil {
return bytes.Buffer{}, "", exerr.Wrap(err, "").Build()
}
return bfr, "image/png", nil
case CompressionPNGSpeed:
enc := &png.Encoder{CompressionLevel: png.BestSpeed}
err = enc.Encode(&bfr, img)
if err != nil {
return bytes.Buffer{}, "", exerr.Wrap(err, "").Build()
}
return bfr, "image/png", nil
case CompressionPNGBest:
enc := &png.Encoder{CompressionLevel: png.BestCompression}
err = enc.Encode(&bfr, img)
if err != nil {
return bytes.Buffer{}, "", exerr.Wrap(err, "").Build()
}
return bfr, "image/png", nil
case CompressionJPEG100:
err = jpeg.Encode(&bfr, img, &jpeg.Options{Quality: 100})
if err != nil {
return bytes.Buffer{}, "", exerr.Wrap(err, "").Build()
}
return bfr, "image/jpeg", nil
case CompressionJPEG90:
err = jpeg.Encode(&bfr, img, &jpeg.Options{Quality: 90})
if err != nil {
return bytes.Buffer{}, "", exerr.Wrap(err, "").Build()
}
return bfr, "image/jpeg", nil
case CompressionJPEG80:
err = jpeg.Encode(&bfr, img, &jpeg.Options{Quality: 80})
if err != nil {
return bytes.Buffer{}, "", exerr.Wrap(err, "").Build()
}
return bfr, "image/jpeg", nil
case CompressionJPEG70:
err = jpeg.Encode(&bfr, img, &jpeg.Options{Quality: 70})
if err != nil {
return bytes.Buffer{}, "", exerr.Wrap(err, "").Build()
}
return bfr, "image/jpeg", nil
case CompressionJPEG60:
err = jpeg.Encode(&bfr, img, &jpeg.Options{Quality: 60})
if err != nil {
return bytes.Buffer{}, "", exerr.Wrap(err, "").Build()
}
return bfr, "image/jpeg", nil
case CompressionJPEG50:
err = jpeg.Encode(&bfr, img, &jpeg.Options{Quality: 50})
if err != nil {
return bytes.Buffer{}, "", exerr.Wrap(err, "").Build()
}
return bfr, "image/jpeg", nil
case CompressionJPEG25:
err = jpeg.Encode(&bfr, img, &jpeg.Options{Quality: 25})
if err != nil {
return bytes.Buffer{}, "", exerr.Wrap(err, "").Build()
}
return bfr, "image/jpeg", nil
case CompressionJPEG10:
err = jpeg.Encode(&bfr, img, &jpeg.Options{Quality: 10})
if err != nil {
return bytes.Buffer{}, "", exerr.Wrap(err, "").Build()
}
return bfr, "image/jpeg", nil
case CompressionJPEG1:
err = jpeg.Encode(&bfr, img, &jpeg.Options{Quality: 1})
if err != nil {
return bytes.Buffer{}, "", exerr.Wrap(err, "").Build()
}
return bfr, "image/jpeg", nil
default:
return bytes.Buffer{}, "", exerr.New(exerr.TypeInternal, "unknown compression method: "+compression.String()).Build()
}
}
func ObjectFitImage(img image.Image, bbw float64, bbh float64, fit ImageFit, fillColor color.Color) (image.Image, error) {
iw := img.Bounds().Size().X
ih := img.Bounds().Size().Y
// [iw, ih] is the size of the image
// [bbw, bbh] is the target bounding box,
// - it specifies the target ratio
// - and the maximal target resolution
facW := float64(iw) / bbw
facH := float64(ih) / bbh
// facW is the ratio between iw and bbw
// - it is the factor by which the bounding box must be multiplied to reach the image size (in the x-axis)
//
// (same is true for facH, but for the height and y-axis)
if fit == ImageFitCover {
// image-fit:cover completely fills the target-bounding-box, it potentially cuts parts of the image away
// we use the smaller (!) value of facW and facH, because we want to have the smallest possible destination rect (due to file size)
// and because the image is made to completely fill the bounding-box, the smaller factor (= teh dimension the image is stretched more) is relevant
// but we cap `fac` at 1 (can be larger than 1)
// a value >1 would mean the final image resolution is biger than the bounding box, which we do not want.
// if the initial image (iw, ih) is already bigger than the bounding box (bbw, bbh), facW and facH are always >1 and fac will be 1
// which means we will simply use the bounding box as destination rect (and scale the image down)
fac := mathext.Clamp(mathext.Min(facW, facH), 0.0, 1.0)
// we scale the bounding box by fac (both dimension the same amount, to keep the bounding-box ratio)
w := int(math.Round(bbw * fac))
h := int(math.Round(bbh * fac))
img = imaging.Fill(img, w, h, imaging.Center, imaging.Lanczos)
newImg := image.NewRGBA(image.Rect(0, 0, w, h))
draw.Draw(newImg, newImg.Bounds(), &image.Uniform{C: fillColor}, image.Pt(0, 0), draw.Src)
draw.Draw(newImg, newImg.Bounds(), img, image.Pt(0, 0), draw.Over)
return newImg, nil
}
if fit == ImageFitContainCenter || fit == ImageFitContainTopLeft || fit == ImageFitContainTopRight || fit == ImageFitContainBottomLeft || fit == ImageFitContainBottomRight {
// image-fit:cover fills the target-bounding-box with the image, there is potentially empty-space, it potentially cuts parts of the image away
// we use the bigger (!) value of facW and facH,
// because the image is made to fit the bounding-box, the bigger factor (= the dimension the image is stretched less) is relevant
// but we cap `fac` at 1 (can be larger than 1)
// a value >1 would mean the final image resolution is biger than the bounding box, which we do not want.
// if the initial image (iw, ih) is already bigger than the bounding box (bbw, bbh), facW and facH are always >1 and fac will be 1
// which means we will simply use the bounding box as destination rect (and scale the image down)
facOut := mathext.Clamp(mathext.Max(facW, facH), 0.0, 1.0)
// we scale the bounding box by fac (both dimension the same amount, to keep the bounding-box ratio)
// [ow|oh] ==> size of output image (same ratio as bounding box [bbw|bbh])
ow := int(math.Round(bbw * facOut))
oh := int(math.Round(bbh * facOut))
facScale := mathext.Min(float64(ow)/float64(iw), float64(oh)/float64(ih))
// [dw|dh] ==> size of destination rect (where to draw source in output image) (same ratio as input image [iw|ih])
dw := int(math.Round(float64(iw) * facScale))
dh := int(math.Round(float64(ih) * facScale))
img = imaging.Resize(img, dw, dh, imaging.Lanczos)
var destBounds image.Rectangle
if fit == ImageFitContainCenter {
destBounds = image.Rect((ow-dw)/2, (oh-dh)/2, (ow-dw)/2+dw, (oh-dh)/2+dh)
} else if fit == ImageFitContainTopLeft {
destBounds = image.Rect(0, 0, dw, dh)
} else if fit == ImageFitContainTopRight {
destBounds = image.Rect(ow-dw, 0, ow, dh)
} else if fit == ImageFitContainBottomLeft {
destBounds = image.Rect(0, oh-dh, dw, oh)
} else if fit == ImageFitContainBottomRight {
destBounds = image.Rect(ow-dw, oh-dh, ow, oh)
}
newImg := image.NewRGBA(image.Rect(0, 0, ow, oh))
draw.Draw(newImg, newImg.Bounds(), &image.Uniform{C: fillColor}, image.Pt(0, 0), draw.Src)
draw.Draw(newImg, destBounds, img, image.Pt(0, 0), draw.Over)
return newImg, nil
}
if fit == ImageFitStretch {
// image-fit:stretch simply stretches the image to the bounding box
// we use the bigger value of [facW;facH], to (potentially) scale the bounding box down before applying it
// theoretically we could directly use [bbw, bbh] in the call to imaging.Resize,
// but if the image is (a lot) smaller than the bouding box it is useful to scale it down to reduce final pdf filesize
// we also cap fac at 1, because we never want the final rect to be bigger than the inputted bounding box (see comments at start of method)
fac := mathext.Clamp(mathext.Max(facW, facH), 0.0, 1.0)
// we scale the bounding box by fac (both dimension the same amount, to keep the bounding-box ratio)
w := int(math.Round(bbw * fac))
h := int(math.Round(bbh * fac))
img = imaging.Resize(img, w, h, imaging.Lanczos)
newImg := image.NewRGBA(image.Rect(0, 0, w, h))
draw.Draw(newImg, newImg.Bounds(), &image.Uniform{C: fillColor}, image.Pt(0, 0), draw.Src)
draw.Draw(newImg, newImg.Bounds(), img, image.Pt(0, 0), draw.Over)
return newImg, nil
}
return nil, exerr.New(exerr.TypeInternal, fmt.Sprintf("unknown image-fit: '%s'", fit)).Build()
}
func VerifyAndDecodeImage(data io.Reader, mime string) (image.Image, error) {
if mime == "image/jpeg" {
img, err := jpeg.Decode(data)
if err != nil {
return nil, exerr.Wrap(err, "failed to decode blob as jpeg").WithType(exerr.TypeInvalidImage).Build()
}
return img, nil
}
if mime == "image/png" {
img, err := png.Decode(data)
if err != nil {
return nil, exerr.Wrap(err, "failed to decode blob as png").WithType(exerr.TypeInvalidImage).Build()
}
return img, nil
}
return nil, exerr.New(exerr.TypeInvalidMimeType, fmt.Sprintf("unknown/invalid image mimetype: '%s'", mime)).Build()
}

View File

@@ -59,6 +59,18 @@ func ArrUnique[T comparable](array []T) []T {
return result return result
} }
func ArrUniqueStable[T comparable](array []T) []T {
hist := make(map[T]bool, len(array))
result := make([]T, 0, len(array))
for _, v := range array {
if _, ok := hist[v]; !ok {
hist[v] = true
result = append(result, v)
}
}
return result
}
func ArrEqualsExact[T comparable](arr1 []T, arr2 []T) bool { func ArrEqualsExact[T comparable](arr1 []T, arr2 []T) bool {
if len(arr1) != len(arr2) { if len(arr1) != len(arr2) {
return false return false
@@ -462,6 +474,17 @@ func ArrAppend[T any](arr []T, add ...T) []T {
return r return r
} }
// ArrPrepend works similar to append(x, y, z) - but doe snot touch the old array and creates a new one
// Also - in contrast to ArrAppend - the add values are inserted at the start of the resulting array (in reverse order)
func ArrPrepend[T any](arr []T, add ...T) []T {
out := make([]T, len(arr)+len(add))
copy(out[len(add):], arr)
for i := 0; i < len(add); i++ {
out[len(add)-i-1] = add[i]
}
return out
}
// ArrCopy does a shallow copy of the 'in' array // ArrCopy does a shallow copy of the 'in' array
func ArrCopy[T any](in []T) []T { func ArrCopy[T any](in []T) []T {
out := make([]T, len(in)) out := make([]T, len(in))

View File

@@ -2,6 +2,7 @@ package langext
import ( import (
"gogs.mikescher.com/BlackForestBytes/goext/tst" "gogs.mikescher.com/BlackForestBytes/goext/tst"
"strings"
"testing" "testing"
) )
@@ -10,3 +11,13 @@ func TestJoinString(t *testing.T) {
res := JoinString(ids, ",") res := JoinString(ids, ",")
tst.AssertEqual(t, res, "1,2,3") tst.AssertEqual(t, res, "1,2,3")
} }
func TestArrPrepend(t *testing.T) {
v1 := []string{"1", "2", "3"}
v2 := ArrPrepend(v1, "4", "5", "6")
tst.AssertEqual(t, strings.Join(v1, ""), "123")
tst.AssertEqual(t, strings.Join(v2, ""), "654123")
}

View File

@@ -77,6 +77,14 @@ func Coalesce4Opt[T any](v1 *T, v2 *T, v3 *T, v4 *T) *T {
return v4 return v4
} }
func CoalesceDblPtr[T any](v1 **T, v2 *T) *T {
if v1 != nil {
return *v1
}
return v2
}
func CoalesceString(s *string, def string) string { func CoalesceString(s *string, def string) string {
if s == nil { if s == nil {
return def return def

View File

@@ -22,6 +22,13 @@ func DblPtr[T any](v T) **T {
return &v_ return &v_
} }
func DblPtrIfNotNil[T any](v *T) **T {
if v == nil {
return nil
}
return &v
}
func DblPtrNil[T any]() **T { func DblPtrNil[T any]() **T {
var v *T = nil var v *T = nil
return &v return &v

View File

@@ -5,6 +5,7 @@ import (
"go.mongodb.org/mongo-driver/bson/bsoncodec" "go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsontype" "go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson/primitive"
"gogs.mikescher.com/BlackForestBytes/goext/exerr"
"gogs.mikescher.com/BlackForestBytes/goext/langext" "gogs.mikescher.com/BlackForestBytes/goext/langext"
"gogs.mikescher.com/BlackForestBytes/goext/rfctime" "gogs.mikescher.com/BlackForestBytes/goext/rfctime"
"reflect" "reflect"
@@ -34,6 +35,15 @@ func CreateGoExtBsonRegistry() *bsoncodec.Registry {
rb.RegisterTypeDecoder(reflect.TypeOf(rfctime.SecondsF64(0)), rfctime.SecondsF64(0)) rb.RegisterTypeDecoder(reflect.TypeOf(rfctime.SecondsF64(0)), rfctime.SecondsF64(0))
rb.RegisterTypeDecoder(reflect.TypeOf(langext.Ptr(rfctime.SecondsF64(0))), rfctime.SecondsF64(0)) rb.RegisterTypeDecoder(reflect.TypeOf(langext.Ptr(rfctime.SecondsF64(0))), rfctime.SecondsF64(0))
rb.RegisterTypeDecoder(reflect.TypeOf(exerr.ErrorCategory{}), exerr.ErrorCategory{})
rb.RegisterTypeDecoder(reflect.TypeOf(langext.Ptr(exerr.ErrorCategory{})), exerr.ErrorCategory{})
rb.RegisterTypeDecoder(reflect.TypeOf(exerr.ErrorSeverity{}), exerr.ErrorSeverity{})
rb.RegisterTypeDecoder(reflect.TypeOf(langext.Ptr(exerr.ErrorSeverity{})), exerr.ErrorSeverity{})
rb.RegisterTypeDecoder(reflect.TypeOf(exerr.ErrorType{}), exerr.ErrorType{})
rb.RegisterTypeDecoder(reflect.TypeOf(langext.Ptr(exerr.ErrorType{})), exerr.ErrorType{})
bsoncodec.DefaultValueEncoders{}.RegisterDefaultEncoders(rb) bsoncodec.DefaultValueEncoders{}.RegisterDefaultEncoders(rb)
bsoncodec.DefaultValueDecoders{}.RegisterDefaultDecoders(rb) bsoncodec.DefaultValueDecoders{}.RegisterDefaultDecoders(rb)

View File

@@ -156,7 +156,7 @@ func SubtractYears(t time.Time, yearCount float64, tz *time.Location) time.Time
intCount, floatCount := math.Modf(yearCount) intCount, floatCount := math.Modf(yearCount)
t.AddDate(-int(intCount), 0, 0) t = t.AddDate(-int(intCount), 0, 0)
t0 := TimeToYearStart(t, tz) t0 := TimeToYearStart(t, tz)
t1 := TimeToYearEnd(t, tz) t1 := TimeToYearEnd(t, tz)
@@ -173,7 +173,7 @@ func AddYears(t time.Time, yearCount float64, tz *time.Location) time.Time {
intCount, floatCount := math.Modf(yearCount) intCount, floatCount := math.Modf(yearCount)
t.AddDate(int(intCount), 0, 0) t = t.AddDate(int(intCount), 0, 0)
t0 := TimeToYearStart(t, tz) t0 := TimeToYearStart(t, tz)
t1 := TimeToYearEnd(t, tz) t1 := TimeToYearEnd(t, tz)

158
timeext/time_test.go Normal file
View File

@@ -0,0 +1,158 @@
package timeext
import (
"testing"
"time"
)
func TestTimeToDayStart(t *testing.T) {
tz := TimezoneBerlin
tm := time.Date(2022, 1, 1, 13, 14, 15, 0, tz)
expected := time.Date(2022, 1, 1, 0, 0, 0, 0, tz)
result := TimeToDayStart(tm, tz)
if !result.Equal(expected) {
t.Errorf("Expected %v but got %v", expected, result)
}
}
func TestTimeToDayEnd(t *testing.T) {
tz := TimezoneBerlin
tm := time.Date(2022, 1, 1, 13, 14, 15, 0, tz)
expected := time.Date(2022, 1, 2, 0, 0, 0, 0, tz).Add(-1)
result := TimeToDayEnd(tm, tz)
if !result.Equal(expected) {
t.Errorf("Expected %v but got %v", expected, result)
}
}
func TestIsSameDayIncludingDateBoundaries(t *testing.T) {
tz := TimezoneBerlin
t1 := time.Date(2022, 1, 1, 23, 59, 59, 0, tz)
t2 := time.Date(2022, 1, 2, 0, 0, 0, 0, tz)
if !IsSameDayIncludingDateBoundaries(t1, t2, tz) {
t.Errorf("Expected %v and %v to be the same day", t1, t2)
}
}
func TestIsDatePartEqual(t *testing.T) {
tz := TimezoneBerlin
t1 := time.Date(2022, 1, 1, 23, 59, 59, 0, tz)
t2 := time.Date(2022, 1, 1, 0, 0, 0, 0, tz)
if !IsDatePartEqual(t1, t2, tz) {
t.Errorf("Expected %v and %v to have the same date part", t1, t2)
}
}
func TestWithTimePart(t *testing.T) {
tz := TimezoneBerlin
base := time.Date(2022, 1, 1, 0, 0, 0, 0, tz)
expected := time.Date(2022, 1, 1, 13, 14, 15, 0, tz)
result := WithTimePart(base, 13, 14, 15)
if !result.Equal(expected) {
t.Errorf("Expected %v but got %v", expected, result)
}
}
func TestCombineDateAndTime(t *testing.T) {
tz := TimezoneBerlin
d := time.Date(2022, 1, 1, 0, 0, 0, 0, tz)
tm := time.Date(0, 0, 0, 13, 14, 15, 0, tz)
expected := time.Date(2022, 1, 1, 13, 14, 15, 0, tz)
result := CombineDateAndTime(d, tm)
if !result.Equal(expected) {
t.Errorf("Expected %v but got %v", expected, result)
}
}
func TestIsSunday(t *testing.T) {
tz := TimezoneBerlin
tm := time.Date(2022, 1, 2, 0, 0, 0, 0, tz) // 2nd January 2022 is a Sunday
if !IsSunday(tm, tz) {
t.Errorf("Expected %v to be a Sunday", tm)
}
}
func TestDurationFromTime(t *testing.T) {
expected := time.Duration(13*time.Hour + 14*time.Minute + 15*time.Second)
result := DurationFromTime(13, 14, 15)
if result != expected {
t.Errorf("Expected %v but got %v", expected, result)
}
}
func TestMin(t *testing.T) {
t1 := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)
t2 := time.Date(2022, 1, 2, 0, 0, 0, 0, time.UTC)
expected := t1
result := Min(t1, t2)
if !result.Equal(expected) {
t.Errorf("Expected %v but got %v", expected, result)
}
}
func TestMax(t *testing.T) {
t1 := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)
t2 := time.Date(2022, 1, 2, 0, 0, 0, 0, time.UTC)
expected := t2
result := Max(t1, t2)
if !result.Equal(expected) {
t.Errorf("Expected %v but got %v", expected, result)
}
}
func TestUnixFloatSeconds(t *testing.T) {
v := 1640995200.0 // 1st January 2022 00:00:00 UTC in Unix timestamp
expected := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)
result := UnixFloatSeconds(v)
if !result.Equal(expected) {
t.Errorf("Expected %v but got %v", expected, result)
}
}
func TestFloorTime(t *testing.T) {
tm := time.Date(2022, 1, 1, 13, 14, 15, 0, time.UTC)
expected := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)
result := FloorTime(tm)
if !result.Equal(expected) {
t.Errorf("Expected %v but got %v", expected, result)
}
}
func TestSubtractYears(t *testing.T) {
tz := TimezoneBerlin
tm := time.Date(2022, 1, 1, 0, 0, 0, 0, tz)
expected := time.Date(2021, 1, 1, 0, 0, 0, 0, tz)
result := SubtractYears(tm, 1, tz)
if !result.Equal(expected) {
t.Errorf("Expected %v but got %v", expected, result)
}
expected = time.Date(2020, 1, 1, 0, 0, 0, 0, tz)
result = SubtractYears(tm, 2, tz)
if !result.Equal(expected) {
t.Errorf("Expected %v but got %v", expected, result)
}
expected = time.Date(2019, 1, 1, 0, 0, 0, 0, tz)
result = SubtractYears(tm, 3, tz)
if !result.Equal(expected) {
t.Errorf("Expected %v but got %v", expected, result)
}
expected = time.Date(2025, 1, 1, 0, 0, 0, 0, tz)
result = SubtractYears(tm, -3, tz)
if !result.Equal(expected) {
t.Errorf("Expected %v but got %v", expected, result)
}
}
func TestAddYears(t *testing.T) {
tz := TimezoneBerlin
tm := time.Date(2022, 1, 1, 0, 0, 0, 0, tz)
expected := time.Date(2023, 1, 1, 0, 0, 0, 0, tz)
result := AddYears(tm, 1, tz)
if !result.Equal(expected) {
t.Errorf("Expected %v but got %v", expected, result)
}
}

View File

@@ -52,6 +52,7 @@ type Coll[TData any] struct {
customDecoder *func(ctx context.Context, dec Decodable) (TData, error) // custom decoding function (useful if TData is an interface) customDecoder *func(ctx context.Context, dec Decodable) (TData, error) // custom decoding function (useful if TData is an interface)
isInterfaceDataType bool // true if TData is an interface (not a struct) isInterfaceDataType bool // true if TData is an interface (not a struct)
unmarshalHooks []func(d TData) TData // called for every object after unmarshalling unmarshalHooks []func(d TData) TData // called for every object after unmarshalling
marshalHooks []func(d TData) TData // called for every object before marshalling
extraModPipeline []func(ctx context.Context) mongo.Pipeline // appended to pipelines after filter/limit/skip/sort, used for $lookup, $set, $unset, $project, etc extraModPipeline []func(ctx context.Context) mongo.Pipeline // appended to pipelines after filter/limit/skip/sort, used for $lookup, $set, $unset, $project, etc
} }
@@ -83,18 +84,32 @@ func (c *Coll[TData]) WithDecodeFunc(cdf func(ctx context.Context, dec Decodable
return c return c
} }
// WithUnmarshalHook
// function that is called for every object after reading from DB
func (c *Coll[TData]) WithUnmarshalHook(fn func(d TData) TData) *Coll[TData] { func (c *Coll[TData]) WithUnmarshalHook(fn func(d TData) TData) *Coll[TData] {
c.unmarshalHooks = append(c.unmarshalHooks, fn) c.unmarshalHooks = append(c.unmarshalHooks, fn)
return c return c
} }
// WithMarshalHook
// function that is called for every object before writing to DB
func (c *Coll[TData]) WithMarshalHook(fn func(d TData) TData) *Coll[TData] {
c.marshalHooks = append(c.marshalHooks, fn)
return c
}
// WithModifyingPipeline
// pipeline that is appended to all read operations (after filtering)
func (c *Coll[TData]) WithModifyingPipeline(p mongo.Pipeline) *Coll[TData] { func (c *Coll[TData]) WithModifyingPipeline(p mongo.Pipeline) *Coll[TData] {
c.extraModPipeline = append(c.extraModPipeline, func(ctx context.Context) mongo.Pipeline { return p }) c.extraModPipeline = append(c.extraModPipeline, func(ctx context.Context) mongo.Pipeline { return p })
return c return c
} }
// WithModifyingPipelineFunc
// pipeline that is appended to all read operations (after filtering)
func (c *Coll[TData]) WithModifyingPipelineFunc(fn func(ctx context.Context) mongo.Pipeline) *Coll[TData] { func (c *Coll[TData]) WithModifyingPipelineFunc(fn func(ctx context.Context) mongo.Pipeline) *Coll[TData] {
c.extraModPipeline = append(c.extraModPipeline, fn) c.extraModPipeline = append(c.extraModPipeline, fn)

View File

@@ -9,6 +9,10 @@ import (
) )
func (c *Coll[TData]) InsertOne(ctx context.Context, valueIn TData) (TData, error) { func (c *Coll[TData]) InsertOne(ctx context.Context, valueIn TData) (TData, error) {
for _, hook := range c.marshalHooks {
valueIn = hook(valueIn)
}
insRes, err := c.coll.InsertOne(ctx, valueIn) insRes, err := c.coll.InsertOne(ctx, valueIn)
if err != nil { if err != nil {
return *new(TData), exerr.Wrap(err, "mongo-query[insert-one] failed").Str("collection", c.Name()).Build() return *new(TData), exerr.Wrap(err, "mongo-query[insert-one] failed").Str("collection", c.Name()).Build()
@@ -36,6 +40,12 @@ func (c *Coll[TData]) InsertOneUnchecked(ctx context.Context, valueIn any) (TDat
} }
func (c *Coll[TData]) InsertMany(ctx context.Context, valueIn []TData) (*mongo.InsertManyResult, error) { func (c *Coll[TData]) InsertMany(ctx context.Context, valueIn []TData) (*mongo.InsertManyResult, error) {
for _, hook := range c.marshalHooks {
for i := 0; i < len(valueIn); i++ {
valueIn[i] = hook(valueIn[i])
}
}
insRes, err := c.coll.InsertMany(ctx, langext.ArrayToInterface(valueIn)) insRes, err := c.coll.InsertMany(ctx, langext.ArrayToInterface(valueIn))
if err != nil { if err != nil {
return nil, exerr.Wrap(err, "mongo-query[insert-many] failed").Int("len(valueIn)", len(valueIn)).Str("collection", c.Name()).Build() return nil, exerr.Wrap(err, "mongo-query[insert-many] failed").Int("len(valueIn)", len(valueIn)).Str("collection", c.Name()).Build()

View File

@@ -61,6 +61,10 @@ func (c *Coll[TData]) UpdateMany(ctx context.Context, filterQuery bson.M, update
} }
func (c *Coll[TData]) ReplaceOne(ctx context.Context, filterQuery bson.M, value TData) error { func (c *Coll[TData]) ReplaceOne(ctx context.Context, filterQuery bson.M, value TData) error {
for _, hook := range c.marshalHooks {
value = hook(value)
}
_, err := c.coll.UpdateOne(ctx, filterQuery, bson.M{"$set": value}) _, err := c.coll.UpdateOne(ctx, filterQuery, bson.M{"$set": value})
if err != nil { if err != nil {
return exerr.Wrap(err, "mongo-query[replace-one] failed"). return exerr.Wrap(err, "mongo-query[replace-one] failed").
@@ -73,6 +77,10 @@ func (c *Coll[TData]) ReplaceOne(ctx context.Context, filterQuery bson.M, value
} }
func (c *Coll[TData]) FindOneAndReplace(ctx context.Context, filterQuery bson.M, value TData) (TData, error) { func (c *Coll[TData]) FindOneAndReplace(ctx context.Context, filterQuery bson.M, value TData) (TData, error) {
for _, hook := range c.marshalHooks {
value = hook(value)
}
mongoRes := c.coll.FindOneAndReplace(ctx, filterQuery, value, options.FindOneAndReplace().SetReturnDocument(options.After)) mongoRes := c.coll.FindOneAndReplace(ctx, filterQuery, value, options.FindOneAndReplace().SetReturnDocument(options.After))
if err := mongoRes.Err(); err != nil { if err := mongoRes.Err(); err != nil {
return *new(TData), exerr.Wrap(err, "mongo-query[find-one-and-update] failed"). return *new(TData), exerr.Wrap(err, "mongo-query[find-one-and-update] failed").

9
wpdf/utils.go Normal file
View File

@@ -0,0 +1,9 @@
package wpdf
func hexToColor(c uint32) PDFColor {
return PDFColor{R: int((c >> 16) & 0xFF), G: int((c >> 8) & 0xFF), B: int((c >> 0) & 0xFF)}
}
func rgbToColor(r, g, b int) PDFColor {
return PDFColor{R: r, G: g, B: b}
}

197
wpdf/wpdf.go Normal file
View File

@@ -0,0 +1,197 @@
package wpdf
import (
"bytes"
"github.com/jung-kurt/gofpdf"
)
type WPDFBuilder struct {
b *gofpdf.Fpdf
tr func(string) string
cellHeight float64
cellSpacing float64
fontName PDFFontFamily
fontStyle PDFFontStyle
fontSize float64
}
type PDFMargins struct {
Left float64
Top float64
Right float64
}
type PDFColor struct {
R int
G int
B int
}
func NewPDFBuilder(orientation PDFOrientation, size PDFSize, unicode bool) *WPDFBuilder {
fpdfbuilder := gofpdf.New(string(orientation), "mm", string(size), "")
var tr func(string) string
if unicode {
tr = fpdfbuilder.UnicodeTranslatorFromDescriptor("")
} else {
tr = func(s string) string { return s }
}
b := &WPDFBuilder{
b: fpdfbuilder,
tr: tr,
cellHeight: 5,
cellSpacing: 1,
}
b.SetMargins(PDFMargins{Left: 15, Top: 25, Right: 15}) // default values
b.SetFont(FontHelvetica, Normal, 12) // ensures font is set
return b
}
func (b *WPDFBuilder) FPDF() *gofpdf.Fpdf {
return b.b
}
func (b *WPDFBuilder) SetMargins(v PDFMargins) {
b.b.SetMargins(v.Left, v.Top, v.Right)
}
func (b *WPDFBuilder) AddPage() {
b.b.AddPage()
}
func (b *WPDFBuilder) SetTextColor(cr, cg, cb int) {
b.b.SetTextColor(cr, cg, cb)
}
func (b *WPDFBuilder) GetTextColor() (cr, cg, cb int) {
return b.b.GetTextColor()
}
func (b *WPDFBuilder) SetDrawColor(cr, cg, cb int) {
b.b.SetDrawColor(cr, cg, cb)
}
func (b *WPDFBuilder) GetDrawColor() (cr, cg, cb int) {
return b.b.GetDrawColor()
}
func (b *WPDFBuilder) SetFillColor(cr, cg, cb int) {
b.b.SetFillColor(cr, cg, cb)
}
func (b *WPDFBuilder) GetFillColor() (cr, cg, cb int) {
return b.b.GetFillColor()
}
func (b *WPDFBuilder) SetLineWidth(w float64) {
b.b.SetLineWidth(w)
}
func (b *WPDFBuilder) GetLineWidth() float64 {
return b.b.GetLineWidth()
}
func (b *WPDFBuilder) SetFont(fontName PDFFontFamily, fontStyle PDFFontStyle, fontSize float64) {
b.b.SetFont(string(fontName), string(fontStyle), fontSize)
b.fontName = fontName
b.fontStyle = fontStyle
b.fontSize = fontSize
b.cellHeight = b.b.PointConvert(fontSize)
}
func (b *WPDFBuilder) SetCellSpacing(h float64) {
b.cellSpacing = h
}
func (b *WPDFBuilder) Ln(h float64) {
b.b.Ln(h)
}
func (b *WPDFBuilder) Build() ([]byte, error) {
buf := new(bytes.Buffer)
err := b.b.Output(buf)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (b *WPDFBuilder) SetX(x float64) {
b.b.SetX(x)
}
func (b *WPDFBuilder) IncX(dx float64) {
b.b.SetX(b.b.GetX() + dx)
}
func (b *WPDFBuilder) GetX() float64 {
return b.b.GetX()
}
func (b *WPDFBuilder) SetY(y float64) {
b.b.SetY(y)
}
func (b *WPDFBuilder) GetY() float64 {
return b.b.GetY()
}
func (b *WPDFBuilder) SetXY(x float64, y float64) {
b.b.SetXY(x, y)
}
func (b *WPDFBuilder) GetXY() (x float64, y float64) {
return b.b.GetXY()
}
func (b *WPDFBuilder) GetMargins() (left, top, right, bottom float64) {
return b.b.GetMargins()
}
func (b *WPDFBuilder) GetMarginLeft() float64 {
v, _, _, _ := b.b.GetMargins()
return v
}
func (b *WPDFBuilder) GetMarginTop() float64 {
_, v, _, _ := b.b.GetMargins()
return v
}
func (b *WPDFBuilder) GetMarginRight() float64 {
_, _, v, _ := b.b.GetMargins()
return v
}
func (b *WPDFBuilder) GetMarginBottom() float64 {
_, _, _, v := b.b.GetMargins()
return v
}
func (b *WPDFBuilder) GetPageSize() (width, height float64) {
return b.b.GetPageSize()
}
func (b *WPDFBuilder) GetPageWidth() float64 {
v, _ := b.b.GetPageSize()
return v
}
func (b *WPDFBuilder) GetPageHeight() float64 {
_, v := b.b.GetPageSize()
return v
}
func (b *WPDFBuilder) GetWorkAreaWidth() float64 {
return b.GetPageWidth() - b.GetMarginLeft() - b.GetMarginRight()
}
func (b *WPDFBuilder) GetStringWidth(str string) float64 {
return b.b.GetStringWidth(str)
}

238
wpdf/wpdfCell.go Normal file
View File

@@ -0,0 +1,238 @@
package wpdf
import "gogs.mikescher.com/BlackForestBytes/goext/langext"
type PDFCellOpt struct {
width *float64
height *float64
border *PDFBorder
ln *PDFTextBreak
align *PDFTextAlign
fill *bool
link *int
linkStr *string
fontNameOverride *PDFFontFamily
fontStyleOverride *PDFFontStyle
fontSizeOverride *float64
extraLn *float64
x *float64
autoWidth *bool
textColor *PDFColor
borderColor *PDFColor
fillColor *PDFColor
autoWidthPaddingX *float64
}
func NewPDFCellOpt() *PDFCellOpt {
return &PDFCellOpt{}
}
func (opt *PDFCellOpt) Width(v float64) *PDFCellOpt {
opt.width = &v
return opt
}
func (opt *PDFCellOpt) Height(v float64) *PDFCellOpt {
opt.height = &v
return opt
}
func (opt *PDFCellOpt) Border(v PDFBorder) *PDFCellOpt {
opt.border = &v
return opt
}
func (opt *PDFCellOpt) LnPos(v PDFTextBreak) *PDFCellOpt {
opt.ln = &v
return opt
}
func (opt *PDFCellOpt) Align(v PDFTextAlign) *PDFCellOpt {
opt.align = &v
return opt
}
func (opt *PDFCellOpt) FillBackground(v bool) *PDFCellOpt {
opt.fill = &v
return opt
}
func (opt *PDFCellOpt) Link(v int) *PDFCellOpt {
opt.link = &v
return opt
}
func (opt *PDFCellOpt) LinkStr(v string) *PDFCellOpt {
opt.linkStr = &v
return opt
}
func (opt *PDFCellOpt) Font(fontName PDFFontFamily, fontStyle PDFFontStyle, fontSize float64) *PDFCellOpt {
opt.fontNameOverride = &fontName
opt.fontStyleOverride = &fontStyle
opt.fontSizeOverride = &fontSize
return opt
}
func (opt *PDFCellOpt) FontName(v PDFFontFamily) *PDFCellOpt {
opt.fontNameOverride = &v
return opt
}
func (opt *PDFCellOpt) FontStyle(v PDFFontStyle) *PDFCellOpt {
opt.fontStyleOverride = &v
return opt
}
func (opt *PDFCellOpt) FontSize(v float64) *PDFCellOpt {
opt.fontSizeOverride = &v
return opt
}
func (opt *PDFCellOpt) Bold() *PDFCellOpt {
opt.fontStyleOverride = langext.Ptr(Bold)
return opt
}
func (opt *PDFCellOpt) Italic() *PDFCellOpt {
opt.fontStyleOverride = langext.Ptr(Italic)
return opt
}
func (opt *PDFCellOpt) LnAfter(v float64) *PDFCellOpt {
opt.extraLn = &v
return opt
}
func (opt *PDFCellOpt) X(v float64) *PDFCellOpt {
opt.x = &v
return opt
}
func (opt *PDFCellOpt) AutoWidth() *PDFCellOpt {
opt.autoWidth = langext.PTrue
return opt
}
func (opt *PDFCellOpt) AutoWidthPaddingX(v float64) *PDFCellOpt {
opt.autoWidthPaddingX = &v
return opt
}
func (opt *PDFCellOpt) TextColor(cr, cg, cb int) *PDFCellOpt {
opt.textColor = langext.Ptr(rgbToColor(cr, cg, cb))
return opt
}
func (opt *PDFCellOpt) TextColorHex(c uint32) *PDFCellOpt {
opt.textColor = langext.Ptr(hexToColor(c))
return opt
}
func (opt *PDFCellOpt) BorderColor(cr, cg, cb int) *PDFCellOpt {
opt.borderColor = langext.Ptr(rgbToColor(cr, cg, cb))
return opt
}
func (opt *PDFCellOpt) BorderColorHex(c uint32) *PDFCellOpt {
opt.borderColor = langext.Ptr(hexToColor(c))
return opt
}
func (opt *PDFCellOpt) FillColor(cr, cg, cb int) *PDFCellOpt {
opt.fillColor = langext.Ptr(rgbToColor(cr, cg, cb))
return opt
}
func (opt *PDFCellOpt) FillColorHex(c uint32) *PDFCellOpt {
opt.fillColor = langext.Ptr(hexToColor(c))
return opt
}
func (b *WPDFBuilder) Cell(txt string, opts ...*PDFCellOpt) {
txtTR := b.tr(txt)
width := float64(0)
height := b.cellHeight + b.cellSpacing
border := BorderNone
ln := BreakToNextLine
align := AlignLeft
fill := false
link := 0
linkStr := ""
var fontNameOverride *PDFFontFamily
var fontStyleOverride *PDFFontStyle
var fontSizeOverride *float64
extraLn := float64(0)
var x *float64
autoWidth := false
var textColor *PDFColor
var borderColor *PDFColor
var fillColor *PDFColor
autoWidthPaddingX := float64(0)
for _, opt := range opts {
width = langext.Coalesce(opt.width, width)
height = langext.Coalesce(opt.height, height)
border = langext.Coalesce(opt.border, border)
ln = langext.Coalesce(opt.ln, ln)
align = langext.Coalesce(opt.align, align)
fill = langext.Coalesce(opt.fill, fill)
link = langext.Coalesce(opt.link, link)
linkStr = langext.Coalesce(opt.linkStr, linkStr)
fontNameOverride = langext.CoalesceOpt(opt.fontNameOverride, fontNameOverride)
fontStyleOverride = langext.CoalesceOpt(opt.fontStyleOverride, fontStyleOverride)
fontSizeOverride = langext.CoalesceOpt(opt.fontSizeOverride, fontSizeOverride)
extraLn = langext.Coalesce(opt.extraLn, extraLn)
x = langext.CoalesceOpt(opt.x, x)
autoWidth = langext.Coalesce(opt.autoWidth, autoWidth)
textColor = langext.CoalesceOpt(opt.textColor, textColor)
borderColor = langext.CoalesceOpt(opt.borderColor, borderColor)
fillColor = langext.CoalesceOpt(opt.fillColor, fillColor)
autoWidthPaddingX = langext.Coalesce(opt.autoWidthPaddingX, autoWidthPaddingX)
}
if fontNameOverride != nil || fontStyleOverride != nil || fontSizeOverride != nil {
oldFontName := b.fontName
oldFontStyle := b.fontStyle
oldFontSize := b.fontSize
newFontName := langext.Coalesce(fontNameOverride, oldFontName)
newFontStyle := langext.Coalesce(fontStyleOverride, oldFontStyle)
newFontSize := langext.Coalesce(fontSizeOverride, oldFontSize)
b.SetFont(newFontName, newFontStyle, newFontSize)
defer func() { b.SetFont(oldFontName, oldFontStyle, oldFontSize) }()
}
if textColor != nil {
oldColorR, oldColorG, oldColorB := b.b.GetTextColor()
b.SetTextColor(textColor.R, textColor.G, textColor.B)
defer func() { b.SetTextColor(oldColorR, oldColorG, oldColorB) }()
}
if borderColor != nil {
oldColorR, oldColorG, oldColorB := b.b.GetDrawColor()
b.SetDrawColor(borderColor.R, borderColor.G, borderColor.B)
defer func() { b.SetDrawColor(oldColorR, oldColorG, oldColorB) }()
}
if fillColor != nil {
oldColorR, oldColorG, oldColorB := b.b.GetFillColor()
b.SetFillColor(fillColor.R, fillColor.G, fillColor.B)
defer func() { b.SetFillColor(oldColorR, oldColorG, oldColorB) }()
}
if x != nil {
b.b.SetX(*x)
}
if autoWidth {
width = b.b.GetStringWidth(txtTR) + autoWidthPaddingX
}
b.b.CellFormat(width, height, txtTR, string(border), int(ln), string(align), fill, link, linkStr)
if extraLn != 0 {
b.b.Ln(extraLn)
}
}

80
wpdf/wpdfConstants.go Normal file
View File

@@ -0,0 +1,80 @@
package wpdf
type PDFOrientation string
const (
Portrait PDFOrientation = "P"
Landscape PDFOrientation = "L"
)
type PDFSize string
const (
SizeA3 PDFSize = "A3"
SizeA4 PDFSize = "A4"
SizeA5 PDFSize = "A4"
SizeLetter PDFSize = "Letter"
SizeLegal PDFSize = "Legal"
SizeTabloid PDFSize = "Tabloid"
)
type PDFFontFamily string
const (
FontCourier PDFFontFamily = "courier"
FontHelvetica PDFFontFamily = "helvetica"
FontTimes PDFFontFamily = "times"
FontZapfDingbats PDFFontFamily = "zapfdingbats"
FontSymbol PDFFontFamily = "symbol"
)
type PDFFontStyle string
const (
Normal PDFFontStyle = ""
Bold PDFFontStyle = "B"
Italic PDFFontStyle = "I"
BoldItalic PDFFontStyle = "IB"
)
type PDFBorder string
const (
BorderNone PDFBorder = ""
BorderFull PDFBorder = "1"
BorderLeft PDFBorder = "L"
BorderTop PDFBorder = "T"
BorderRight PDFBorder = "R"
BorderBottom PDFBorder = "B"
BorderTLR PDFBorder = "TLR"
BorderLR PDFBorder = "LR"
)
type PDFTextBreak int
const (
BreakToRight PDFTextBreak = 0
BreakToNextLine PDFTextBreak = 1
BreakToBelow PDFTextBreak = 2
)
type PDFTextAlign string
const (
AlignLeft PDFTextAlign = "L"
AlignHorzCenter PDFTextAlign = "C"
AlignRight PDFTextAlign = "R"
)
type PDFRectStyle string
const (
RectFill PDFRectStyle = "F"
RectOutline PDFRectStyle = "D"
RectFillOutline PDFRectStyle = "FD"
)
const (
BackgroundFill = true
BackgroundTransparent = false
)

315
wpdf/wpdfImage.go Normal file
View File

@@ -0,0 +1,315 @@
package wpdf
import (
"bytes"
"github.com/jung-kurt/gofpdf"
"gogs.mikescher.com/BlackForestBytes/goext/imageext"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"image"
"image/color"
"net/http"
)
type PDFImageRef struct {
Info *gofpdf.ImageInfoType
Name string
Bin []byte
Image *image.Image
Mime string
}
type PDFImageRegisterOpt struct {
imageType *string
readDpi *bool
allowNegativePosition *bool
name *string
}
func NewPDFImageRegisterOpt() *PDFImageRegisterOpt {
return &PDFImageRegisterOpt{}
}
func (opt *PDFImageRegisterOpt) ImageType(v string) *PDFImageRegisterOpt {
opt.imageType = &v
return opt
}
func (opt *PDFImageRegisterOpt) ReadDpi(v bool) *PDFImageRegisterOpt {
opt.readDpi = &v
return opt
}
func (opt *PDFImageRegisterOpt) AllowNegativePosition(v bool) *PDFImageRegisterOpt {
opt.allowNegativePosition = &v
return opt
}
func (opt *PDFImageRegisterOpt) Name(v string) *PDFImageRegisterOpt {
opt.name = &v
return opt
}
func (b *WPDFBuilder) RegisterImage(bin []byte, opts ...*PDFImageRegisterOpt) *PDFImageRef {
imgName := "fpdf_img_" + langext.MustRawHexUUID()
imageType := ""
readDpi := false
allowNegativePosition := false
mime := "application/octet-stream"
for _, opt := range opts {
imageType = langext.Coalesce(opt.imageType, imageType)
readDpi = langext.Coalesce(opt.readDpi, readDpi)
allowNegativePosition = langext.Coalesce(opt.allowNegativePosition, allowNegativePosition)
imgName = langext.Coalesce(opt.name, imgName)
}
if imageType == "" {
ct := http.DetectContentType(bin[:512])
switch ct {
case "image/jpg":
imageType = "JPG"
mime = ct
case "image/jpeg":
imageType = "JPEG"
mime = ct
case "image/png":
imageType = "PNG"
mime = ct
case "image/gif":
imageType = "GIF"
mime = ct
}
} else {
switch imageType {
case "JPG":
case "JPEG":
mime = "image/jpeg"
case "PNG":
mime = "image/png"
case "GIF":
mime = "image/gif"
}
}
options := gofpdf.ImageOptions{
ImageType: imageType,
ReadDpi: readDpi,
AllowNegativePosition: allowNegativePosition,
}
info := b.b.RegisterImageOptionsReader(imgName, options, bytes.NewReader(bin))
return &PDFImageRef{
Name: imgName,
Info: info,
Bin: bin,
Image: nil,
Mime: mime,
}
}
type PDFImageOpt struct {
x *float64
y *float64
width *float64
height *float64
flow *bool
link *int
linkStr *string
imageType *string
readDpi *bool
allowNegativePosition *bool
imageFit *imageext.ImageFit
fillColor *color.Color
compression *imageext.ImageCompresson
reEncodePixelPerMM *float64
crop *imageext.ImageCrop
}
func NewPDFImageOpt() *PDFImageOpt {
return &PDFImageOpt{}
}
func (opt *PDFImageOpt) X(v float64) *PDFImageOpt {
opt.x = &v
return opt
}
func (opt *PDFImageOpt) Y(v float64) *PDFImageOpt {
opt.y = &v
return opt
}
func (opt *PDFImageOpt) Width(v float64) *PDFImageOpt {
opt.width = &v
return opt
}
func (opt *PDFImageOpt) Height(v float64) *PDFImageOpt {
opt.height = &v
return opt
}
func (opt *PDFImageOpt) Flow(v bool) *PDFImageOpt {
opt.flow = &v
return opt
}
func (opt *PDFImageOpt) Link(v int) *PDFImageOpt {
opt.link = &v
return opt
}
func (opt *PDFImageOpt) LinkStr(v string) *PDFImageOpt {
opt.linkStr = &v
return opt
}
func (opt *PDFImageOpt) ImageType(v string) *PDFImageOpt {
opt.imageType = &v
return opt
}
func (opt *PDFImageOpt) ReadDpi(v bool) *PDFImageOpt {
opt.readDpi = &v
return opt
}
func (opt *PDFImageOpt) AllowNegativePosition(v bool) *PDFImageOpt {
opt.allowNegativePosition = &v
return opt
}
func (opt *PDFImageOpt) ImageFit(v imageext.ImageFit) *PDFImageOpt {
opt.imageFit = &v
return opt
}
func (opt *PDFImageOpt) FillColor(v color.Color) *PDFImageOpt {
opt.fillColor = &v
return opt
}
func (opt *PDFImageOpt) Compression(v imageext.ImageCompresson) *PDFImageOpt {
opt.compression = &v
return opt
}
func (opt *PDFImageOpt) ReEncodePixelPerMM(v float64) *PDFImageOpt {
opt.reEncodePixelPerMM = &v
return opt
}
func (opt *PDFImageOpt) Crop(cropX float64, cropY float64, cropWidth float64, cropHeight float64) *PDFImageOpt {
opt.crop = &imageext.ImageCrop{
CropX: cropX,
CropY: cropY,
CropWidth: cropWidth,
CropHeight: cropHeight,
}
return opt
}
func (b *WPDFBuilder) Image(img *PDFImageRef, opts ...*PDFImageOpt) {
var err error
x := b.GetX()
y := b.GetY()
w := img.Info.Width()
h := img.Info.Height()
flow := true
link := 0
linkStr := ""
imageType := ""
readDpi := false
allowNegativePosition := false
reEncodePixelPerMM := 15.0
var imageFit *imageext.ImageFit = nil
var fillColor color.Color = color.Transparent
compression := imageext.CompressionPNGSpeed
var crop *imageext.ImageCrop = nil
for _, opt := range opts {
x = langext.Coalesce(opt.x, x)
y = langext.Coalesce(opt.y, y)
w = langext.Coalesce(opt.width, w)
h = langext.Coalesce(opt.height, h)
flow = langext.Coalesce(opt.flow, flow)
link = langext.Coalesce(opt.link, link)
linkStr = langext.Coalesce(opt.linkStr, linkStr)
imageType = langext.Coalesce(opt.imageType, imageType)
readDpi = langext.Coalesce(opt.readDpi, readDpi)
allowNegativePosition = langext.Coalesce(opt.allowNegativePosition, allowNegativePosition)
imageFit = langext.CoalesceOpt(opt.imageFit, imageFit)
fillColor = langext.Coalesce(opt.fillColor, fillColor)
compression = langext.Coalesce(opt.compression, compression)
reEncodePixelPerMM = langext.Coalesce(opt.reEncodePixelPerMM, reEncodePixelPerMM)
crop = langext.CoalesceOpt(opt.crop, crop)
}
regName := img.Name
if imageFit != nil || fillColor != nil || crop != nil {
var dataimg image.Image
if img.Image != nil {
dataimg = *img.Image
} else {
dataimg, err = imageext.VerifyAndDecodeImage(bytes.NewReader(img.Bin), img.Mime)
if err != nil {
b.b.SetError(err)
return
}
}
if crop != nil {
dataimg, err = imageext.CropImage(dataimg, crop.CropX, crop.CropY, crop.CropWidth, crop.CropHeight)
if err != nil {
b.b.SetError(err)
return
}
}
if imageFit != nil {
pdfPixelPerMillimeter := 15.0
pxw := w * pdfPixelPerMillimeter
pxh := h * pdfPixelPerMillimeter
dataimg, err = imageext.ObjectFitImage(dataimg, pxw, pxh, *imageFit, fillColor)
if err != nil {
b.b.SetError(err)
return
}
}
bfr, imgMime, err := imageext.EncodeImage(dataimg, compression)
if err != nil {
b.b.SetError(err)
return
}
regName = regName + "_" + langext.MustRawHexUUID()
switch imgMime {
case "image/jpeg":
imageType = "JPEG"
case "image/png":
imageType = "PNG"
case "image/gif":
imageType = "GIF"
}
b.b.RegisterImageOptionsReader(regName, gofpdf.ImageOptions{ImageType: imageType}, &bfr)
}
fpdfOpt := gofpdf.ImageOptions{
ImageType: imageType,
ReadDpi: readDpi,
AllowNegativePosition: allowNegativePosition,
}
b.b.ImageOptions(regName, x, y, w, h, flow, fpdfOpt, link, linkStr)
}

194
wpdf/wpdfMultiCell.go Normal file
View File

@@ -0,0 +1,194 @@
package wpdf
import "gogs.mikescher.com/BlackForestBytes/goext/langext"
type PDFMultiCellOpt struct {
width *float64
height *float64
border *PDFBorder
align *PDFTextAlign
fill *bool
fontNameOverride *PDFFontFamily
fontStyleOverride *PDFFontStyle
fontSizeOverride *float64
extraLn *float64
x *float64
textColor *PDFColor
borderColor *PDFColor
fillColor *PDFColor
}
func NewPDFMultiCellOpt() *PDFMultiCellOpt {
return &PDFMultiCellOpt{}
}
func (opt *PDFMultiCellOpt) Width(v float64) *PDFMultiCellOpt {
opt.width = &v
return opt
}
func (opt *PDFMultiCellOpt) Height(v float64) *PDFMultiCellOpt {
opt.height = &v
return opt
}
func (opt *PDFMultiCellOpt) Border(v PDFBorder) *PDFMultiCellOpt {
opt.border = &v
return opt
}
func (opt *PDFMultiCellOpt) Align(v PDFTextAlign) *PDFMultiCellOpt {
opt.align = &v
return opt
}
func (opt *PDFMultiCellOpt) FillBackground(v bool) *PDFMultiCellOpt {
opt.fill = &v
return opt
}
func (opt *PDFMultiCellOpt) Font(fontName PDFFontFamily, fontStyle PDFFontStyle, fontSize float64) *PDFMultiCellOpt {
opt.fontNameOverride = &fontName
opt.fontStyleOverride = &fontStyle
opt.fontSizeOverride = &fontSize
return opt
}
func (opt *PDFMultiCellOpt) FontName(v PDFFontFamily) *PDFMultiCellOpt {
opt.fontNameOverride = &v
return opt
}
func (opt *PDFMultiCellOpt) FontStyle(v PDFFontStyle) *PDFMultiCellOpt {
opt.fontStyleOverride = &v
return opt
}
func (opt *PDFMultiCellOpt) FontSize(v float64) *PDFMultiCellOpt {
opt.fontSizeOverride = &v
return opt
}
func (opt *PDFMultiCellOpt) Bold() *PDFMultiCellOpt {
opt.fontStyleOverride = langext.Ptr(Bold)
return opt
}
func (opt *PDFMultiCellOpt) Italic() *PDFMultiCellOpt {
opt.fontStyleOverride = langext.Ptr(Italic)
return opt
}
func (opt *PDFMultiCellOpt) LnAfter(v float64) *PDFMultiCellOpt {
opt.extraLn = &v
return opt
}
func (opt *PDFMultiCellOpt) X(v float64) *PDFMultiCellOpt {
opt.x = &v
return opt
}
func (opt *PDFMultiCellOpt) TextColor(cr, cg, cb int) *PDFMultiCellOpt {
opt.textColor = langext.Ptr(rgbToColor(cr, cg, cb))
return opt
}
func (opt *PDFMultiCellOpt) TextColorHex(c uint32) *PDFMultiCellOpt {
opt.textColor = langext.Ptr(hexToColor(c))
return opt
}
func (opt *PDFMultiCellOpt) BorderColor(cr, cg, cb int) *PDFMultiCellOpt {
opt.borderColor = langext.Ptr(rgbToColor(cr, cg, cb))
return opt
}
func (opt *PDFMultiCellOpt) BorderColorHex(c uint32) *PDFMultiCellOpt {
opt.borderColor = langext.Ptr(hexToColor(c))
return opt
}
func (opt *PDFMultiCellOpt) FillColor(cr, cg, cb int) *PDFMultiCellOpt {
opt.fillColor = langext.Ptr(rgbToColor(cr, cg, cb))
return opt
}
func (opt *PDFMultiCellOpt) FillColorHex(c uint32) *PDFMultiCellOpt {
opt.fillColor = langext.Ptr(hexToColor(c))
return opt
}
func (b *WPDFBuilder) MultiCell(txt string, opts ...*PDFMultiCellOpt) {
txtTR := b.tr(txt)
width := float64(0)
height := b.cellHeight + b.cellSpacing
border := BorderNone
align := AlignLeft
fill := false
var fontNameOverride *PDFFontFamily
var fontStyleOverride *PDFFontStyle
var fontSizeOverride *float64
extraLn := float64(0)
var x *float64
var textColor *PDFColor
var borderColor *PDFColor
var fillColor *PDFColor
for _, opt := range opts {
width = langext.Coalesce(opt.width, width)
height = langext.Coalesce(opt.height, height)
border = langext.Coalesce(opt.border, border)
align = langext.Coalesce(opt.align, align)
fill = langext.Coalesce(opt.fill, fill)
fontNameOverride = langext.CoalesceOpt(opt.fontNameOverride, fontNameOverride)
fontStyleOverride = langext.CoalesceOpt(opt.fontStyleOverride, fontStyleOverride)
fontSizeOverride = langext.CoalesceOpt(opt.fontSizeOverride, fontSizeOverride)
extraLn = langext.Coalesce(opt.extraLn, extraLn)
x = langext.CoalesceOpt(opt.x, x)
textColor = langext.CoalesceOpt(opt.textColor, textColor)
borderColor = langext.CoalesceOpt(opt.borderColor, borderColor)
fillColor = langext.CoalesceOpt(opt.fillColor, fillColor)
}
if fontNameOverride != nil || fontStyleOverride != nil || fontSizeOverride != nil {
oldFontName := b.fontName
oldFontStyle := b.fontStyle
oldFontSize := b.fontSize
newFontName := langext.Coalesce(fontNameOverride, oldFontName)
newFontStyle := langext.Coalesce(fontStyleOverride, oldFontStyle)
newFontSize := langext.Coalesce(fontSizeOverride, oldFontSize)
b.SetFont(newFontName, newFontStyle, newFontSize)
defer func() { b.SetFont(oldFontName, oldFontStyle, oldFontSize) }()
}
if textColor != nil {
oldColorR, oldColorG, oldColorB := b.b.GetTextColor()
b.SetTextColor(textColor.R, textColor.G, textColor.B)
defer func() { b.SetTextColor(oldColorR, oldColorG, oldColorB) }()
}
if borderColor != nil {
oldColorR, oldColorG, oldColorB := b.b.GetDrawColor()
b.SetDrawColor(borderColor.R, borderColor.G, borderColor.B)
defer func() { b.SetDrawColor(oldColorR, oldColorG, oldColorB) }()
}
if fillColor != nil {
oldColorR, oldColorG, oldColorB := b.b.GetFillColor()
b.SetFillColor(fillColor.R, fillColor.G, fillColor.B)
defer func() { b.SetFillColor(oldColorR, oldColorG, oldColorB) }()
}
if x != nil {
b.b.SetX(*x)
}
b.b.MultiCell(width, height, txtTR, string(border), string(align), fill)
if extraLn != 0 {
b.b.Ln(extraLn)
}
}

126
wpdf/wpdfRect.go Normal file
View File

@@ -0,0 +1,126 @@
package wpdf
import "gogs.mikescher.com/BlackForestBytes/goext/langext"
type PDFRectOpt struct {
x *float64
y *float64
lineWidth *float64
drawColor *PDFColor
fillColor *PDFColor
radiusTL *float64
radiusTR *float64
radiusBR *float64
radiusBL *float64
}
func NewPDFRectOpt() *PDFRectOpt {
return &PDFRectOpt{}
}
func (opt *PDFRectOpt) X(v float64) *PDFRectOpt {
opt.x = &v
return opt
}
func (opt *PDFRectOpt) Y(v float64) *PDFRectOpt {
opt.y = &v
return opt
}
func (opt *PDFRectOpt) LineWidth(v float64) *PDFRectOpt {
opt.lineWidth = &v
return opt
}
func (opt *PDFRectOpt) DrawColor(cr, cg, cb int) *PDFRectOpt {
opt.drawColor = langext.Ptr(rgbToColor(cr, cg, cb))
return opt
}
func (opt *PDFRectOpt) DrawColorHex(c uint32) *PDFRectOpt {
opt.drawColor = langext.Ptr(hexToColor(c))
return opt
}
func (opt *PDFRectOpt) FillColor(cr, cg, cb int) *PDFRectOpt {
opt.fillColor = langext.Ptr(rgbToColor(cr, cg, cb))
return opt
}
func (opt *PDFRectOpt) FillColorHex(c uint32) *PDFRectOpt {
opt.fillColor = langext.Ptr(hexToColor(c))
return opt
}
func (opt *PDFRectOpt) Rounded(radius float64) *PDFRectOpt {
opt.radiusTL = &radius
opt.radiusTR = &radius
opt.radiusBR = &radius
opt.radiusBL = &radius
return opt
}
func (opt *PDFRectOpt) RadiusTL(radius float64) *PDFRectOpt {
opt.radiusTL = &radius
return opt
}
func (opt *PDFRectOpt) RadiusTR(radius float64) *PDFRectOpt {
opt.radiusTR = &radius
return opt
}
func (opt *PDFRectOpt) RadiusBL(radius float64) *PDFRectOpt {
opt.radiusBL = &radius
return opt
}
func (opt *PDFRectOpt) RadiusBR(radius float64) *PDFRectOpt {
opt.radiusBR = &radius
return opt
}
func (b *WPDFBuilder) Rect(w float64, h float64, styleStr PDFRectStyle, opts ...*PDFRectOpt) {
x := b.GetX()
y := b.GetY()
var lineWidth *float64
var drawColor *PDFColor
var fillColor *PDFColor
radiusTL := float64(0)
radiusTR := float64(0)
radiusBR := float64(0)
radiusBL := float64(0)
for _, opt := range opts {
x = langext.Coalesce(opt.x, x)
y = langext.Coalesce(opt.y, y)
lineWidth = langext.CoalesceOpt(opt.lineWidth, lineWidth)
drawColor = langext.CoalesceOpt(opt.drawColor, drawColor)
fillColor = langext.CoalesceOpt(opt.fillColor, fillColor)
radiusTL = langext.Coalesce(opt.radiusTL, radiusTL)
radiusTR = langext.Coalesce(opt.radiusTR, radiusTR)
radiusBR = langext.Coalesce(opt.radiusBR, radiusBR)
radiusBL = langext.Coalesce(opt.radiusBL, radiusBL)
}
if lineWidth != nil {
old := b.GetLineWidth()
b.SetLineWidth(*lineWidth)
defer func() { b.SetLineWidth(old) }()
}
if drawColor != nil {
oldR, oldG, oldB := b.GetDrawColor()
b.SetDrawColor(drawColor.R, drawColor.G, drawColor.B)
defer func() { b.SetDrawColor(oldR, oldG, oldB) }()
}
if fillColor != nil {
oldR, oldG, oldB := b.GetFillColor()
b.SetFillColor(fillColor.R, fillColor.G, fillColor.B)
defer func() { b.SetFillColor(oldR, oldG, oldB) }()
}
b.b.RoundedRectExt(x, y, w, h, radiusTL, radiusTR, radiusBR, radiusBL, string(styleStr))
}