update zen lib

This commit is contained in:
Tigor Hutasuhut 2024-08-22 17:26:53 +07:00
parent 10296fb4dd
commit f71f1115fb
18 changed files with 542 additions and 41 deletions

7
Makefile Normal file
View file

@ -0,0 +1,7 @@
.ONESHELL:
generate: generate-proto
generate-proto:
cd ./schemas/proto
buf generate

View file

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View file

@ -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
View 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=

View 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
// }

View file

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

View file

@ -47,6 +47,7 @@ message Payload {
bytes e_json = 6;
string e_text = 7;
}
string id = 8;
}
message Service {

View file

@ -1 +0,0 @@
package server

View file

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

View file

@ -1 +0,0 @@
package zlog