feat: Initial

This commit is contained in:
Artёm Mishin committed 2025-04-25 11:20:31 +03:00
commit f0abca60db
26 files changed
+1112

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/universe.iml" filepath="$PROJECT_DIR$/.idea/universe.iml" />
</modules>
</component>
</project>
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
View File
Whitespace-only changes.
+89
View File
@@ -0,0 +1,89 @@
package app
import (
"go.mrlokop.dev/universe/core/config"
"go.mrlokop.dev/universe/core/errors"
"go.mrlokop.dev/universe/core/modules"
"go.uber.org/fx"
"go.uber.org/fx/fxevent"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"sync/atomic"
)
var (
ErrAlreadyStarted = errors.Type("ErrAlreadyStarted", "already started")
ErrUnknownModule = errors.Type("ErrUnknownModule", "specified module does not exist")
)
type Application interface {
Config() *config.UniverseConfig
Run() error
}
type UniverseApplication struct {
fx *fx.App
config *config.UniverseConfig
options []fx.Option
started atomic.Bool
}
func (app *UniverseApplication) Config() *config.UniverseConfig {
return app.config
}
func (app *UniverseApplication) Run() error {
if !app.started.CompareAndSwap(false, true) {
return ErrAlreadyStarted.CreateEmpty()
}
options := make([]fx.Option, 0)
options = append(options, app.options...)
options = append(options,
fx.Provide(func() Application {
return app
}),
fx.WithLogger(func(log *zap.Logger) fxevent.Logger {
logger := &fxevent.ZapLogger{Logger: log.WithOptions(zap.AddCallerSkip(2))}
logger.UseLogLevel(zapcore.DebugLevel)
return logger
}),
)
if app.config.Modules != nil && len(app.config.Modules) > 0 {
for id, modConf := range app.config.Modules {
kind := modConf["kind"].(string)
module := modules.GetModule(kind)
if module == nil {
return ErrUnknownModule.Builder().
Messagef("module '%s' is not registered", kind).
WithField("module", kind).
Build()
}
option, err := module.Create(id, modConf)
if err != nil {
return err
}
options = append(options, option)
}
}
app.fx = fx.New(options...)
app.fx.Run()
return nil
}
func (app *UniverseApplication) WithOption(options ...fx.Option) *UniverseApplication {
if app.started.Load() {
panic(ErrAlreadyStarted)
}
app.options = append(app.options, options...)
return app
}
func NewUniverseApplication(config *config.UniverseConfig) *UniverseApplication {
return &UniverseApplication{config: config}
}
+53
View File
@@ -0,0 +1,53 @@
package app
import (
"go.mrlokop.dev/universe/core/config"
"go.mrlokop.dev/universe/core/fs"
"go.mrlokop.dev/universe/core/modules"
"go.uber.org/zap"
"os"
)
var (
Module = modules.SimpleModule("universe:core", func(context modules.ModuleFactoryContext) error {
context.Invoke(func(log *zap.Logger, info *Info, env AppEnvType) {
hostname, err := os.Hostname()
if err != nil {
hostname = "unknown"
}
log.Sugar().Infof("Starting %s on %s, pid=%v, env=%s", info.ServiceName, hostname, os.Getpid(), env)
})
context.Provide(func(env AppEnvType) RunPath {
if env == AppEnvTypeDevelopment {
return fs.System.INodeAt("run")
}
runEnv := os.Getenv("UNIVERSE_RUN_PATH")
if runEnv == "" {
return fs.System.INodeAt(".")
}
return fs.System.INodeAt(runEnv)
})
context.Provide(func(path RunPath) StoragePath {
return path.Join("storage")
})
context.Provide(provideEnv)
context.Provide(provideAppInfo)
context.Provide(func(app Application) *config.UniverseConfig {
return app.Config()
})
context.Provide(func(env AppEnvType) *zap.Logger {
if env == AppEnvTypeDevelopment {
config := zap.NewDevelopmentConfig()
config.OutputPaths = []string{"stdout"}
return zap.Must(config.Build(
zap.WithCaller(true),
))
}
return zap.Must(zap.NewProduction())
})
return nil
})
)
+56
View File
@@ -0,0 +1,56 @@
package app
import (
"os"
)
type AppEnvType int
type AppEnvName string
const (
AppEnvTypeDevelopment AppEnvType = iota
AppEnvTypeStaging
AppEnvTypeProduction
)
func (app AppEnvType) String() string {
switch app {
case AppEnvTypeDevelopment:
return "development"
case AppEnvTypeStaging:
return "staging"
case AppEnvTypeProduction:
return "production"
default:
return "unknown"
}
}
var (
knownEnvTypeEnvs = []string{
"UNIVERSE_ENV",
"APP_ENV",
}
)
func provideEnv() AppEnvType {
for _, envType := range knownEnvTypeEnvs {
value := os.Getenv(envType)
if value == "" {
continue
}
switch envType {
case "development", "dev", "devel":
return AppEnvTypeDevelopment
case "staging", "test":
return AppEnvTypeStaging
case "production", "prod":
return AppEnvTypeProduction
default:
return AppEnvTypeDevelopment
}
}
return AppEnvTypeDevelopment
}
+44
View File
@@ -0,0 +1,44 @@
package app
import (
"go.mrlokop.dev/universe/core/config"
"reflect"
)
type Info struct {
ServiceName string
Version string
BuildInfo *BuildInfo
}
type BuildInfo struct {
GitCommit string
BuildDate string
}
func provideAppInfo(
app Application,
cfg *config.UniverseConfig,
) *Info {
serviceInfo := cfg.ServiceInfo
if serviceInfo == nil {
serviceInfo = &config.ServiceInfo{}
// extract service name from app
serviceInfo.Name = extractServiceName(app)
}
return &Info{
ServiceName: serviceInfo.Name,
BuildInfo: &BuildInfo{
GitCommit: "",
BuildDate: "",
},
}
}
func extractServiceName(app Application) string {
appType := reflect.TypeOf(app)
if appType.Kind() == reflect.Ptr {
return appType.Elem().Name()
}
return appType.Name()
}
+6
View File
@@ -0,0 +1,6 @@
package app
import "go.mrlokop.dev/universe/core/fs"
type StoragePath fs.INode
type RunPath fs.INode
+11
View File
@@ -0,0 +1,11 @@
package app
import "fmt"
func Group(name string) string {
return fmt.Sprintf(`group:"%s"`, name)
}
func Name(name string) string {
return fmt.Sprintf(`name:"%s"`, name)
}
+37
View File
@@ -0,0 +1,37 @@
package main
import (
ua "go.mrlokop.dev/universe/core/app"
uc "go.mrlokop.dev/universe/core/config"
"go.uber.org/fx"
"log"
"time"
)
func main() {
runDirectory := "run"
cfg := new(uc.UniverseConfig)
err := uc.LoadConfigFromDirectory(runDirectory, cfg)
if err != nil {
log.Fatalf("Error loading config: %v", err)
}
app := ua.NewUniverseApplication(cfg)
create, err := ua.Module.Create("$base", make(map[string]interface{}))
if err != nil {
log.Fatalf("Error creating base module: %v", err)
}
app.WithOption(
fx.StartTimeout(10*time.Minute),
fx.Supply(cfg),
create,
)
err = app.Run()
if err != nil {
log.Fatalln(err.Error())
}
}
+62
View File
@@ -0,0 +1,62 @@
package config
import (
"errors"
"gopkg.in/yaml.v3"
"os"
"path/filepath"
)
type UniverseConfig struct {
ServiceInfo *ServiceInfo `yaml:"service_info" json:"service_info" bson:"service_info"`
Modules map[string]map[string]interface{} `yaml:"modules" json:"modules" bson:"modules"`
}
var (
wellKnownConfigPlaces []string = []string{
"config.yaml",
"config.yml",
}
)
func LoadConfigFromDirectory[T any](dir string, out *T) error {
for _, fileName := range wellKnownConfigPlaces {
path := filepath.Join(dir, fileName)
if _, err := os.Stat(path); os.IsNotExist(err) {
continue
}
return LoadConfigFromPath(path, out)
}
return nil
}
func LoadConfigFromPath[T any](path string, out *T) error {
ext := filepath.Ext(path)
switch ext {
case ".yaml", ".yml":
// Load YAML config
if err := LoadYamlConfigFromPath(path, out); err != nil {
return err
}
return nil
default:
return errors.New("Universe config file must be .yaml or .yml")
}
}
func LoadYamlConfigFromPath[T any](path string, out *T) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
decoder := yaml.NewDecoder(file)
if err := decoder.Decode(out); err != nil {
return err
}
return nil
}
+5
View File
@@ -0,0 +1,5 @@
package config
type ServiceInfo struct {
Name string `yaml:"name" json:"name" bson:"name"`
}
+69
View File
@@ -0,0 +1,69 @@
package errors
import "fmt"
type UniverseErrorBuilder struct {
t *UniverseErrorType
message string
cause error
fields map[interface{}]any
stack ErrorStack
}
func Builder() *UniverseErrorBuilder {
return &UniverseErrorBuilder{
stack: CaptureStackIfEnabled(),
}
}
func BuilderEmpty() *UniverseErrorBuilder {
return &UniverseErrorBuilder{}
}
func (b *UniverseErrorBuilder) Message(message string) *UniverseErrorBuilder {
b.message = message
return b
}
func (b *UniverseErrorBuilder) Messagef(format string, args ...interface{}) *UniverseErrorBuilder {
b.message = fmt.Sprintf(format, args...)
return b
}
func (b *UniverseErrorBuilder) Type(typeName *UniverseErrorType) *UniverseErrorBuilder {
b.t = typeName
return b
}
func (b *UniverseErrorBuilder) Cause(cause error) *UniverseErrorBuilder {
b.cause = cause
return b
}
func (b *UniverseErrorBuilder) CaptureStackIfEnabled() *UniverseErrorBuilder {
b.stack = CaptureStackIfEnabled()
return b
}
func (b *UniverseErrorBuilder) CaptureStack() *UniverseErrorBuilder {
b.stack = CaptureStack()
return b
}
func (b *UniverseErrorBuilder) WithField(key interface{}, value interface{}) *UniverseErrorBuilder {
if b.fields == nil {
b.fields = make(map[interface{}]any)
}
b.fields[key] = value
return b
}
func (b *UniverseErrorBuilder) Build() UniverseError {
return &UniverseErrorImpl{
msg: b.message,
cause: b.cause,
fields: b.fields,
stack: b.stack,
t: b.t,
}
}
+72
View File
@@ -0,0 +1,72 @@
package errors
type UniverseError interface {
Error() string
Type() *UniverseErrorType
Message() string
Cause() error
StackTrace() ErrorStack
GetAny(key interface{}) any
With(key interface{}, value any) UniverseError
Builder() *UniverseErrorBuilder
}
type UniverseErrorImpl struct {
msg string
cause error
t *UniverseErrorType
stack ErrorStack
fields map[interface{}]any
}
func (e *UniverseErrorImpl) Type() *UniverseErrorType {
return e.t
}
func (e *UniverseErrorImpl) StackTrace() ErrorStack {
return e.stack
}
func (e *UniverseErrorImpl) With(field interface{}, value any) UniverseError {
if e.fields == nil {
e.fields = map[interface{}]any{}
}
e.fields[field] = value
return e
}
func (e *UniverseErrorImpl) Builder() *UniverseErrorBuilder {
return &UniverseErrorBuilder{
t: e.t,
message: e.msg,
cause: e.cause,
fields: e.fields,
stack: e.stack,
}
}
func (e *UniverseErrorImpl) Error() string {
return ErrorToString(e)
}
func (e *UniverseErrorImpl) Unwrap() error {
return e.cause
}
func (e *UniverseErrorImpl) Message() string {
return e.msg
}
func (e *UniverseErrorImpl) Cause() error {
return e.cause
}
func (e *UniverseErrorImpl) GetAny(field interface{}) any {
if e.fields == nil {
return nil
}
return e.fields[field]
}
+47
View File
@@ -0,0 +1,47 @@
package errors
import (
"io"
"strings"
)
func ErrorToString(e error) string {
builder := &strings.Builder{}
AppendError(builder, e, 0)
return builder.String()
}
func AppendError(writer io.StringWriter, err error, depth uint) {
padding := strings.Repeat(" ", int(depth))
var uniError UniverseError
if As(err, &uniError) {
errorType := uniError.Type()
if errorType != nil {
_, _ = writer.WriteString(errorType.Name)
_, _ = writer.WriteString(": ")
}
_, _ = writer.WriteString(uniError.Message())
stack := uniError.StackTrace()
if stack != nil {
for _, value := range stack {
_, _ = writer.WriteString("\n")
_, _ = writer.WriteString(padding)
_, _ = writer.WriteString(value)
}
}
cause := uniError.Cause()
if cause != nil {
_, _ = writer.WriteString("\n")
_, _ = writer.WriteString(padding)
_, _ = writer.WriteString("Caused by: ")
AppendError(writer, cause, depth+1)
}
} else {
_, _ = writer.WriteString(err.Error())
}
}
+27
View File
@@ -0,0 +1,27 @@
package errors
import (
"runtime"
"strings"
)
var (
IsStackCaptureEnabled = true
StackCaptureSize = 1024 * 4
)
type ErrorStack = []string
func CaptureStackIfEnabled() ErrorStack {
if !IsStackCaptureEnabled {
return nil
}
return CaptureStack()
}
func CaptureStack() ErrorStack {
buffer := make([]byte, StackCaptureSize)
stackSize := runtime.Stack(buffer, false)
return strings.Split(string(buffer[:stackSize]), "\n")
}
+84
View File
@@ -0,0 +1,84 @@
package errors
import "fmt"
type UniverseErrorType struct {
error
ParentType *UniverseErrorType
Name string
DefaultMessage string
}
func (e *UniverseErrorType) Builder() *UniverseErrorBuilder {
return &UniverseErrorBuilder{
t: e,
stack: CaptureStackIfEnabled(),
}
}
func (e *UniverseErrorType) CreateWithCause(cause error) UniverseError {
return &UniverseErrorImpl{
t: e,
cause: cause,
msg: e.DefaultMessage,
stack: CaptureStackIfEnabled(),
}
}
func (e *UniverseErrorType) CreateWithMessage(message string) UniverseError {
return &UniverseErrorImpl{
t: e,
msg: message,
stack: CaptureStackIfEnabled(),
}
}
func (e *UniverseErrorType) Create(message string, cause error) UniverseError {
return &UniverseErrorImpl{
t: e,
msg: message,
cause: cause,
stack: CaptureStackIfEnabled(),
}
}
func (e *UniverseErrorType) CreateEmpty() UniverseError {
return &UniverseErrorImpl{
t: e,
msg: e.DefaultMessage,
stack: CaptureStackIfEnabled(),
}
}
func (e *UniverseErrorType) Is(err error) bool {
return Is(err, e)
}
func (e *UniverseErrorType) Child(childName string) *UniverseErrorType {
return &UniverseErrorType{
ParentType: e,
Name: childName,
}
}
func (e *UniverseErrorType) Error() string {
return fmt.Sprintf("type: %s", e.Name)
}
func (e *UniverseErrorType) Panic() {
panic(e.CreateEmpty())
}
func Type(name string, defaultMessage string) *UniverseErrorType {
return &UniverseErrorType{
Name: name,
DefaultMessage: defaultMessage,
}
}
func New(message string) *UniverseErrorType {
return &UniverseErrorType{
DefaultMessage: message,
}
}
+127
View File
@@ -0,0 +1,127 @@
package errors
import (
"reflect"
)
func Wrap(err error, message string) error {
return &UniverseErrorImpl{
msg: message,
cause: err,
stack: CaptureStackIfEnabled(),
}
}
func Get[T *any](err error, field string) T {
if err == nil {
return nil
}
var uerr UniverseError
if As(err, &uerr) {
value := uerr.GetAny(field)
if value, ok := value.(T); ok {
return value
}
}
return nil
}
func Unwrap(err error) error {
u, ok := err.(interface {
Unwrap() error
})
if !ok {
return nil
}
return u.Unwrap()
}
func Is(err, target error) bool {
if err == nil || target == nil {
return err == target
}
isComparable := reflect.TypeOf(target).Comparable()
return is(err, target, isComparable)
}
func is(err, target error, targetComparable bool) bool {
for {
if targetComparable && err == target {
return true
}
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
switch x := err.(type) {
case interface{ Unwrap() error }:
err = x.Unwrap()
if err == nil {
return false
}
case interface{ Unwrap() []error }:
for _, err := range x.Unwrap() {
if is(err, target, targetComparable) {
return true
}
}
return false
default:
return false
}
}
}
func As(err error, target any) bool {
if err == nil {
return false
}
if target == nil {
panic("errors: target cannot be nil")
}
val := reflect.ValueOf(target)
typ := val.Type()
if typ.Kind() != reflect.Ptr || val.IsNil() {
panic("errors: target must be a non-nil pointer")
}
targetType := typ.Elem()
if targetType.Kind() != reflect.Interface && !targetType.Implements(errorType) {
panic("errors: *target must be interface or implement error")
}
return as(err, target, val, targetType)
}
func as(err error, target any, targetVal reflect.Value, targetType reflect.Type) bool {
for {
if reflect.TypeOf(err).AssignableTo(targetType) {
targetVal.Elem().Set(reflect.ValueOf(err))
return true
}
if x, ok := err.(interface{ As(any) bool }); ok && x.As(target) {
return true
}
switch x := err.(type) {
case interface{ Unwrap() error }:
err = x.Unwrap()
if err == nil {
return false
}
case interface{ Unwrap() []error }:
for _, err := range x.Unwrap() {
if err == nil {
continue
}
if as(err, target, targetVal, targetType) {
return true
}
}
return false
default:
return false
}
}
}
var errorType = reflect.TypeOf((*error)(nil)).Elem()
+28
View File
@@ -0,0 +1,28 @@
package fs
import (
"io"
"os"
)
type FileSystem interface {
INodeAt(path string) INode
}
type INode interface {
IsExists() bool
Stat() (os.FileInfo, error)
MakeDirs() error
RemoveAll() error
Parent() INode
Join(path ...string) INode
ReadIntoBytes() ([]byte, error)
OpenReadStream() (io.ReadCloser, error)
OpenWriteStream() (io.WriteCloser, error)
Name() string
AbsolutePath() string
}
+83
View File
@@ -0,0 +1,83 @@
package fs
import (
"io"
"os"
"path/filepath"
)
type sys struct {
}
type sysInode struct {
path string
}
func (s *sysInode) RemoveAll() error {
return os.RemoveAll(s.path)
}
func (s *sysInode) Join(path ...string) INode {
return &sysInode{
path: filepath.Join(path...),
}
}
func (s *sysInode) MakeDirs() error {
return os.MkdirAll(s.path, 0755)
}
func (s *sysInode) Parent() INode {
return &sysInode{
path: filepath.Dir(s.path),
}
}
func (s *sysInode) ReadIntoBytes() ([]byte, error) {
return os.ReadFile(s.path)
}
func (s *sysInode) OpenReadStream() (io.ReadCloser, error) {
f, err := os.Open(s.path)
if err != nil {
return nil, err
}
return f, nil
}
func (s *sysInode) OpenWriteStream() (io.WriteCloser, error) {
return os.OpenFile(s.path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
}
func (s *sysInode) Stat() (os.FileInfo, error) {
return os.Stat(s.path)
}
func (s *sysInode) IsExists() bool {
if _, err := os.Stat(s.path); os.IsNotExist(err) {
return false
}
return true
}
func (s *sysInode) Name() string {
return filepath.Base(s.path)
}
func (s *sysInode) AbsolutePath() string {
abs, err := filepath.Abs(s.path)
if err != nil {
return s.path
}
return abs
}
func (s *sys) INodeAt(path string) INode {
return &sysInode{
path: path,
}
}
var (
System FileSystem = &sys{}
)
+20
View File
@@ -0,0 +1,20 @@
module go.mrlokop.dev/universe/core
go 1.24
require (
github.com/mitchellh/mapstructure v1.5.0
go.uber.org/fx v1.23.0
go.uber.org/zap v1.27.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/kr/pretty v0.3.1 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/stretchr/testify v1.10.0 // indirect
go.uber.org/dig v1.18.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/sys v0.31.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)
+37
View File
@@ -0,0 +1,37 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw=
go.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
go.uber.org/fx v1.23.0 h1:lIr/gYWQGfTwGcSXWXu4vP5Ws6iqnNEIY+F/aFzCKTg=
go.uber.org/fx v1.23.0/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+120
View File
@@ -0,0 +1,120 @@
package modules
import (
"fmt"
"github.com/mitchellh/mapstructure"
"go.uber.org/fx"
"go.uber.org/zap"
)
type ModuleConfiguration interface {
Extract(destination any) error
}
var registry map[string]Module = map[string]Module{}
func RegisterModule(module Module) {
registry[module.Name()] = module
}
func GetModule(name string) Module {
return registry[name]
}
type Module interface {
Name() string
Create(id string, moduleConfiguration map[string]interface{}) (fx.Option, error)
}
type ModuleFactoryContext interface {
Id() string
Kind() string
NameTag() string
GroupTag() string
ConfigureLogger(logger *zap.Logger) *zap.Logger
ExtractConfig(destination any) error
AddOption(option fx.Option)
Provide(constructors ...interface{})
Invoke(funcs ...interface{})
}
type simpleFactoryContext struct {
id string
kind string
config map[string]interface{}
options []fx.Option
}
func (s *simpleFactoryContext) Kind() string {
return s.kind
}
func (s *simpleFactoryContext) ConfigureLogger(logger *zap.Logger) *zap.Logger {
return logger.Named(fmt.Sprintf("%s[%s]", s.Kind(), s.id))
}
func (s *simpleFactoryContext) Provide(constructors ...interface{}) {
s.AddOption(fx.Provide(constructors...))
}
func (s *simpleFactoryContext) Invoke(funcs ...interface{}) {
s.AddOption(fx.Invoke(funcs...))
}
func (s *simpleFactoryContext) Id() string {
return s.id
}
func (s *simpleFactoryContext) NameTag() string {
return fmt.Sprintf(`name:"%s"`, s.id)
}
func (s *simpleFactoryContext) GroupTag() string {
return fmt.Sprintf(`group:"%s"`, s.id)
}
func (s *simpleFactoryContext) ExtractConfig(destination any) error {
return mapstructure.Decode(s.config, destination)
}
func (s *simpleFactoryContext) AddOption(option fx.Option) {
s.options = append(s.options, option)
}
type simpleFactory = func(context ModuleFactoryContext) error
type simple struct {
name string
factory simpleFactory
}
func (s simple) Name() string {
return s.name
}
func (s simple) Create(id string, config map[string]interface{}) (fx.Option, error) {
ctx := &simpleFactoryContext{
options: make([]fx.Option, 0),
config: config,
id: id,
kind: s.name,
}
err := s.factory(ctx)
if err != nil {
return nil, err
}
return fx.Options(ctx.options...), nil
}
func SimpleModule(name string, factory simpleFactory) Module {
module := &simple{name: name, factory: factory}
RegisterModule(module)
return module
}
+4
View File
@@ -0,0 +1,4 @@
package utils
type Consumer[T any] func(T)
type Supplier[T any] func() T