update zen lib
This commit is contained in:
parent
10296fb4dd
commit
f71f1115fb
7
Makefile
Normal file
7
Makefile
Normal file
|
@ -0,0 +1,7 @@
|
|||
.ONESHELL:
|
||||
|
||||
generate: generate-proto
|
||||
|
||||
generate-proto:
|
||||
cd ./schemas/proto
|
||||
buf generate
|
|
@ -1,4 +1,8 @@
|
|||
package main
|
||||
|
||||
import "gitlab.bareksa.com/backend/zen/cmd/zen/serve"
|
||||
|
||||
// This is the main entry point for CLI interface.
|
||||
func main() {}
|
||||
func main() {
|
||||
serve.Serve()
|
||||
}
|
||||
|
|
33
cmd/zen/serve/serve.go
Normal file
33
cmd/zen/serve/serve.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
package serve
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"connectrpc.com/grpcreflect"
|
||||
"gitlab.bareksa.com/backend/zen/internal/gen/proto/notify/v1/notifyv1connect"
|
||||
"gitlab.bareksa.com/backend/zen/internal/rpchandler"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/h2c"
|
||||
)
|
||||
|
||||
func Serve() {
|
||||
mux := http.NewServeMux()
|
||||
reflector := grpcreflect.NewStaticReflector(
|
||||
notifyv1connect.NotifyServiceName,
|
||||
)
|
||||
|
||||
mux.Handle(grpcreflect.NewHandlerV1(reflector))
|
||||
mux.Handle(grpcreflect.NewHandlerV1Alpha(reflector))
|
||||
mux.Handle(notifyv1connect.NewNotifyServiceHandler(rpchandler.NotifyServiceHandler{}))
|
||||
|
||||
slog.Info("Starting server on :8080")
|
||||
err := http.ListenAndServe(
|
||||
":8080",
|
||||
h2c.NewHandler(mux, &http2.Server{}),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
64
core/zerr/error.go
Normal file
64
core/zerr/error.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package zerr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"gitlab.bareksa.com/backend/zen/core/zoptions"
|
||||
)
|
||||
|
||||
type Error interface {
|
||||
error
|
||||
// LogValue returns log fields to be consumed by logger.
|
||||
LogValue() slog.Value
|
||||
|
||||
// Code sets error code.
|
||||
Code(code connect.Code) Error
|
||||
GetCode() connect.Code
|
||||
|
||||
// Message sets error message. It uses fmt.Sprintf to format the message.
|
||||
Message(msg string, args ...any) Error
|
||||
GetMessage() string
|
||||
|
||||
PublicMessage(msg string, args ...any) Error
|
||||
GetPublicMessage() string
|
||||
|
||||
Caller(pc uintptr) Error
|
||||
GetCaller() uintptr
|
||||
|
||||
// Details sets error details. It's a key-value pair where the odd index is the key and the even index is the value.
|
||||
//
|
||||
// Invalid key-value pair number and format will cause the misplaced value to be paired up with "!BADKEY".
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// err.Details(
|
||||
// "key1", 12345,
|
||||
// "key2", float64(12345.67),
|
||||
// )
|
||||
Details(fields ...any) Error
|
||||
GetDetails() []any
|
||||
|
||||
// Time sets the error time.
|
||||
//
|
||||
// Time is already set to current when the error is created or wrapped,
|
||||
// so this method is only useful when you want to set a different time.
|
||||
Time(t time.Time) Error
|
||||
GetTime() time.Time
|
||||
|
||||
// ID sets the error ID.
|
||||
//
|
||||
// ID is used to identify the error. Used by Zen to consider if an error is the same as another.
|
||||
//
|
||||
// Current implementation
|
||||
ID(msg string, args ...any) Error
|
||||
GetID() string
|
||||
|
||||
Log(ctx context.Context) Error
|
||||
Notify(ctx context.Context, opts ...zoptions.NotifyOption) Error
|
||||
|
||||
// Sequence returns the state of the error sequence.
|
||||
Sequence() *Sequence
|
||||
}
|
212
core/zerr/implementation.go
Normal file
212
core/zerr/implementation.go
Normal file
|
@ -0,0 +1,212 @@
|
|||
package zerr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"gitlab.bareksa.com/backend/zen/core/zoptions"
|
||||
)
|
||||
|
||||
type Err struct {
|
||||
message string
|
||||
publicMessage string
|
||||
code connect.Code
|
||||
errs []error
|
||||
caller uintptr
|
||||
details []any
|
||||
time time.Time
|
||||
id string
|
||||
logger Logger
|
||||
notifier Notifier
|
||||
sequence *Sequence
|
||||
}
|
||||
|
||||
func (mu *Err) Error() string {
|
||||
if len(mu.errs) == 0 {
|
||||
return "[nil]"
|
||||
}
|
||||
s := strings.Builder{}
|
||||
if len(mu.errs) == 1 {
|
||||
str := getNestedErrorString(mu.errs[0])
|
||||
str, found := strings.CutPrefix(str, mu.message)
|
||||
if found {
|
||||
str, _ = strings.CutPrefix(str, ": ")
|
||||
}
|
||||
s.WriteString(mu.message)
|
||||
s.WriteString(": ")
|
||||
s.WriteString(str)
|
||||
return s.String()
|
||||
}
|
||||
s.WriteString(mu.message)
|
||||
for i, e := range mu.errs {
|
||||
if i > 0 {
|
||||
s.WriteString("\n")
|
||||
}
|
||||
s.WriteString(strconv.Itoa(i + 1))
|
||||
s.WriteString(". ")
|
||||
s.WriteString(getNestedErrorString(e))
|
||||
}
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func getNestedErrorString(err error) string {
|
||||
if err == nil {
|
||||
return "[nil]"
|
||||
}
|
||||
s := strings.Builder{}
|
||||
if e, ok := err.(interface{ Unwrap() error }); ok {
|
||||
current := err.Error()
|
||||
if err := e.Unwrap(); err != nil {
|
||||
next := getNestedErrorString(err)
|
||||
hasPrefix := strings.HasPrefix(next, current)
|
||||
if !hasPrefix {
|
||||
s.WriteString(current)
|
||||
s.WriteString(": ")
|
||||
}
|
||||
s.WriteString(next)
|
||||
}
|
||||
}
|
||||
|
||||
if errs, ok := err.(interface{ Unwrap() []error }); ok {
|
||||
for i, e := range errs.Unwrap() {
|
||||
if i > 0 {
|
||||
s.WriteString("\n")
|
||||
}
|
||||
s.WriteString(strconv.Itoa(i + 1))
|
||||
s.WriteString(". ")
|
||||
s.WriteString(getNestedErrorString(e))
|
||||
}
|
||||
}
|
||||
|
||||
if s.Len() == 0 {
|
||||
s.WriteString(err.Error())
|
||||
}
|
||||
|
||||
return s.String()
|
||||
}
|
||||
|
||||
// LogValue returns log fields to be consumed by logger.
|
||||
func (mu *Err) LogValue() slog.Value {
|
||||
attrs := make([]slog.Attr, 0, len(mu.errs))
|
||||
i := 0
|
||||
for _, err := range mu.errs {
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
i++
|
||||
attrs = append(attrs, slog.Attr{
|
||||
Key: strconv.Itoa(i),
|
||||
Value: errorValuer{err}.LogValue(),
|
||||
})
|
||||
}
|
||||
return slog.GroupValue(attrs...)
|
||||
}
|
||||
|
||||
// Code sets error code. Ignored in Multi.
|
||||
func (mu *Err) Code(code connect.Code) Error {
|
||||
mu.code = code
|
||||
return mu
|
||||
}
|
||||
|
||||
// GetCode Always returns 0 in Multi.
|
||||
func (mu *Err) GetCode() connect.Code {
|
||||
return mu.code
|
||||
}
|
||||
|
||||
// Message sets error message. It uses fmt.Sprintf to format the message.
|
||||
func (mu *Err) Message(msg string, args ...any) Error {
|
||||
mu.message = fmt.Sprintf(msg, args...)
|
||||
return mu
|
||||
}
|
||||
|
||||
func (mu *Err) GetMessage() string {
|
||||
return mu.message
|
||||
}
|
||||
|
||||
func (mu *Err) PublicMessage(msg string, args ...any) Error {
|
||||
mu.publicMessage = fmt.Sprintf(msg, args...)
|
||||
return mu
|
||||
}
|
||||
|
||||
func (mu *Err) GetPublicMessage() string {
|
||||
return mu.publicMessage
|
||||
}
|
||||
|
||||
func (mu *Err) Caller(pc uintptr) Error {
|
||||
mu.caller = pc
|
||||
return mu
|
||||
}
|
||||
|
||||
func (mu *Err) GetCaller() uintptr {
|
||||
return mu.caller
|
||||
}
|
||||
|
||||
// Details sets error details. It's a key-value pair where the odd index is the key and the even index is the value.
|
||||
//
|
||||
// Invalid key-value pair number and format will cause the misplaced value to be paired up with "!BADKEY".
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// err.Details(
|
||||
// "key1", 12345,
|
||||
// "key2", float64(12345.67),
|
||||
// )
|
||||
func (mu *Err) Details(fields ...any) Error {
|
||||
mu.details = fields
|
||||
return mu
|
||||
}
|
||||
|
||||
func (mu *Err) GetDetails() []any {
|
||||
return mu.details
|
||||
}
|
||||
|
||||
// Time sets the error time.
|
||||
//
|
||||
// Time is already set to current when the error is created or wrapped,
|
||||
// so this method is only useful when you want to set a different time.
|
||||
func (mu *Err) Time(t time.Time) Error {
|
||||
mu.time = t
|
||||
return mu
|
||||
}
|
||||
|
||||
func (mu *Err) GetTime() time.Time {
|
||||
return mu.time
|
||||
}
|
||||
|
||||
// ID sets the error ID.
|
||||
//
|
||||
// ID is used to identify the error. Used by Zen to consider if an error is the same as another.
|
||||
//
|
||||
// Current implementation
|
||||
func (mu *Err) ID(msg string, args ...any) Error {
|
||||
mu.id = fmt.Sprintf(msg, args...)
|
||||
return mu
|
||||
}
|
||||
|
||||
func (mu *Err) GetID() string {
|
||||
return mu.id
|
||||
}
|
||||
|
||||
func (mu *Err) Log(ctx context.Context) Error {
|
||||
mu.logger.Log(ctx, mu)
|
||||
return mu
|
||||
}
|
||||
|
||||
func (mu *Err) Notify(ctx context.Context, opts ...zoptions.NotifyOption) Error {
|
||||
mu.notifier.Notify(ctx, mu, opts...)
|
||||
return mu
|
||||
}
|
||||
|
||||
// Sequence returns the state of the error sequence.
|
||||
func (mu *Err) Sequence() *Sequence {
|
||||
return mu.sequence
|
||||
}
|
||||
|
||||
func (mu *Err) Unwrap() []error {
|
||||
return mu.errs
|
||||
}
|
98
core/zerr/sequence.go
Normal file
98
core/zerr/sequence.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
package zerr
|
||||
|
||||
import "iter"
|
||||
|
||||
// Sequence is the implementation of zerr.Sequence.
|
||||
//
|
||||
// The implementation is reversed linked list.
|
||||
//
|
||||
// New Sequence value will be added to the start of the list.
|
||||
type Sequence struct {
|
||||
prev *Sequence
|
||||
next *Sequence
|
||||
err Error
|
||||
index int
|
||||
}
|
||||
|
||||
// Set prepends the next Sequence to the current Sequence.
|
||||
func (se *Sequence) Set(next *Sequence) {
|
||||
next.next = se
|
||||
se.prev = next
|
||||
se.index = next.index + 1
|
||||
if next.next != nil {
|
||||
next.next.Set(se)
|
||||
}
|
||||
}
|
||||
|
||||
// Outer returns the error that wraps this Error.
|
||||
// Return nil if this Error is not wrapped
|
||||
// or wrapping outer is not a zerr.Error.
|
||||
func (se *Sequence) Outer() Error {
|
||||
if se.prev != nil {
|
||||
return se.prev.err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Inner returns the inner zerr.Error in the sequence.
|
||||
//
|
||||
// if there is no inner zerr.Error, it returns nil.
|
||||
//
|
||||
// if the inner error is not a zerr.Error, this returns nil.
|
||||
func (se *Sequence) Inner() Error {
|
||||
if se.next != nil {
|
||||
return se.next.err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Index returns the index of this Error in the sequence.
|
||||
func (se *Sequence) Index() int {
|
||||
return se.index
|
||||
}
|
||||
|
||||
// Root returns the outermost Error in the sequence.
|
||||
//
|
||||
// Returns self if there is no outermost.
|
||||
func (se *Sequence) Root() *Sequence {
|
||||
root := se
|
||||
for root.prev != nil {
|
||||
root = root.prev
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
func (se *Sequence) IsRoot() bool {
|
||||
return se.prev == nil
|
||||
}
|
||||
|
||||
// Iter returns an iterator to traverse the sequence.
|
||||
//
|
||||
// The iterator will start from outermost zerr.Error (root) to the last zerr.Error
|
||||
// in depth-first order.
|
||||
//
|
||||
// Iterator will skip any nil values in the sequence.
|
||||
//
|
||||
// Iterator does not guarantee idempotent behavior.
|
||||
// Next call to iterator may start from different root
|
||||
// because the parent in the tree may be wrapped
|
||||
// by another zerr.Error.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// for _, e := range err.Sequence().Iter() {
|
||||
// // e is zerr.Error
|
||||
// // do something with e
|
||||
// }
|
||||
func (se *Sequence) Iter() iter.Seq[Error] {
|
||||
root := se.Root()
|
||||
return iter.Seq[Error](func(yield func(V Error) bool) {
|
||||
for next := root; next != nil; next = next.next {
|
||||
if next.err != nil {
|
||||
if !yield(next.err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
31
core/zerr/valuer.go
Normal file
31
core/zerr/valuer.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package zerr
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type errorValuer struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (ev errorValuer) LogValue() slog.Value {
|
||||
if ev.error == nil {
|
||||
return slog.AnyValue(nil)
|
||||
}
|
||||
if lv, ok := ev.error.(slog.LogValuer); ok {
|
||||
return lv.LogValue()
|
||||
}
|
||||
if unwrap, ok := ev.error.(interface{ Unwrap() []error }); ok {
|
||||
return (&Err{errs: unwrap.Unwrap()}).LogValue()
|
||||
}
|
||||
attrs := make([]slog.Attr, 0, 3)
|
||||
typ := reflect.TypeOf(ev.error).String()
|
||||
attrs = append(attrs, slog.String("type", typ))
|
||||
attrs = append(attrs, slog.String("error", ev.error.Error()))
|
||||
if typ == "*errors.errorString" {
|
||||
return slog.GroupValue(attrs...)
|
||||
}
|
||||
attrs = append(attrs, slog.Any("details", ev.error))
|
||||
return slog.GroupValue(attrs...)
|
||||
}
|
41
core/zerr/wrap.go
Normal file
41
core/zerr/wrap.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package zerr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"gitlab.bareksa.com/backend/zen/core/zoptions"
|
||||
)
|
||||
|
||||
type WrapInitialInput struct {
|
||||
Error error
|
||||
Message string
|
||||
PC uintptr
|
||||
Time time.Time
|
||||
Logger Logger
|
||||
Notifier Notifier
|
||||
}
|
||||
|
||||
type Wrapper interface {
|
||||
// Wrap creates a zerr.Error with inputs
|
||||
// generated by global entry points.
|
||||
Wrap(input WrapInitialInput) Error
|
||||
}
|
||||
|
||||
type Logger interface {
|
||||
Log(ctx context.Context, err Error)
|
||||
}
|
||||
|
||||
type Notifier interface {
|
||||
Notify(ctx context.Context, err Error, opts ...zoptions.NotifyOption)
|
||||
}
|
||||
|
||||
type WrapperFunc func(input WrapInitialInput) Error
|
||||
|
||||
func (wr WrapperFunc) Wrap(input WrapInitialInput) Error {
|
||||
return wr(input)
|
||||
}
|
||||
|
||||
var DefaultWrapper Wrapper = WrapperFunc(func(input WrapInitialInput) Error {
|
||||
panic("not implemented")
|
||||
})
|
7
core/zlog/zlog.go
Normal file
7
core/zlog/zlog.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package zlog
|
||||
|
||||
import notifyv1 "gitlab.bareksa.com/backend/zen/internal/gen/proto/notify/v1"
|
||||
|
||||
func log() {
|
||||
notifyv1.Payload
|
||||
}
|
11
go.mod
11
go.mod
|
@ -1,3 +1,12 @@
|
|||
module gitlab.bareksa.com/backend/zen
|
||||
|
||||
go 1.22.5
|
||||
go 1.23
|
||||
|
||||
require (
|
||||
connectrpc.com/connect v1.16.2
|
||||
connectrpc.com/grpcreflect v1.2.0
|
||||
golang.org/x/net v0.23.0
|
||||
google.golang.org/protobuf v1.34.2
|
||||
)
|
||||
|
||||
require golang.org/x/text v0.14.0 // indirect
|
||||
|
|
12
go.sum
Normal file
12
go.sum
Normal file
|
@ -0,0 +1,12 @@
|
|||
connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE=
|
||||
connectrpc.com/connect v1.16.2/go.mod h1:n2kgwskMHXC+lVqb18wngEpF95ldBHXjZYJussz5FRc=
|
||||
connectrpc.com/grpcreflect v1.2.0 h1:Q6og1S7HinmtbEuBvARLNwYmTbhEGRpHDhqrPNlmK+U=
|
||||
connectrpc.com/grpcreflect v1.2.0/go.mod h1:nwSOKmE8nU5u/CidgHtPYk1PFI3U9ignz7iDMxOYkSY=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
17
internal/rpchandler/rpc_handler.go
Normal file
17
internal/rpchandler/rpc_handler.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package rpchandler
|
||||
|
||||
import (
|
||||
"gitlab.bareksa.com/backend/zen/internal/gen/proto/notify/v1/notifyv1connect"
|
||||
)
|
||||
|
||||
type NotifyServiceHandler struct {
|
||||
notifyv1connect.UnimplementedNotifyServiceHandler
|
||||
}
|
||||
|
||||
// func (no *NotifyServiceHandler) SendNotification(_ context.Context, _ *connect.Request[notifyv1.SendNotificationRequest]) (*connect.Response[notifyv1.SendNotificationResponse], error) {
|
||||
// panic("not implemented") // TODO: Implement
|
||||
// }
|
||||
//
|
||||
// func (no *NotifyServiceHandler) SendAttachment(_ context.Context, _ *connect.ClientStream[notifyv1.SendAttachmentRequest]) (*connect.Response[notifyv1.SendAttachmentResponse], error) {
|
||||
// panic("not implemented") // TODO: Implement
|
||||
// }
|
|
@ -7,18 +7,18 @@ managed:
|
|||
|
||||
override:
|
||||
- file_option: go_package_prefix
|
||||
value: gitlab.bareksa.com/backend/zen/zcore/internal/gen/proto
|
||||
value: gitlab.bareksa.com/backend/zen/internal/gen/proto
|
||||
|
||||
plugins:
|
||||
- remote: buf.build/protocolbuffers/go:v1.34.2
|
||||
out: ../../zcore/internal/gen/proto
|
||||
out: ../../internal/gen/proto
|
||||
opt:
|
||||
- paths=source_relative
|
||||
|
||||
- remote: buf.build/connectrpc/go
|
||||
out: ../../zcore/internal/gen/proto
|
||||
out: ../../internal/gen/proto
|
||||
opt:
|
||||
- paths=source_relative
|
||||
|
||||
- remote: buf.build/community/pseudomuto-doc:v1.5.1
|
||||
out: ../../zcore/internal/gen/proto
|
||||
out: ../../internal/gen/proto
|
||||
|
|
|
@ -47,6 +47,7 @@ message Payload {
|
|||
bytes e_json = 6;
|
||||
string e_text = 7;
|
||||
}
|
||||
string id = 8;
|
||||
}
|
||||
|
||||
message Service {
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
package server
|
|
@ -1,33 +0,0 @@
|
|||
package zerr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"gitlab.bareksa.com/backend/zen/zcore/zoptions"
|
||||
)
|
||||
|
||||
type Error interface {
|
||||
error
|
||||
|
||||
Code(code int) Error
|
||||
GetCode() int
|
||||
|
||||
Message(msg string, args ...any) Error
|
||||
GetMessage() string
|
||||
|
||||
PublicMessage(msg string, args ...any) Error
|
||||
GetPublicMessage() string
|
||||
|
||||
Caller(pc uintptr) Error
|
||||
GetCaller() uintptr
|
||||
|
||||
Time(t time.Time) Error
|
||||
GetTime() time.Time
|
||||
|
||||
Key(msg string, args ...any) Error
|
||||
GetKey() string
|
||||
|
||||
Log(ctx context.Context) Error
|
||||
Notify(ctx context.Context, opts ...zoptions.NotifyOption) Error
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
package zlog
|
Loading…
Reference in a new issue