feat: Initial
This commit is contained in:
commit
f0abca60db
26 files changed
+1112
No files matched your search
Generated
+8
@@ -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
|
||||
Generated
+8
@@ -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>
|
||||
Generated
+9
@@ -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
@@ -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>
|
||||
+89
@@ -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}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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()
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package app
|
||||
|
||||
import "go.mrlokop.dev/universe/core/fs"
|
||||
|
||||
type StoragePath fs.INode
|
||||
type RunPath fs.INode
|
||||
@@ -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
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package config
|
||||
|
||||
type ServiceInfo struct {
|
||||
Name string `yaml:"name" json:"name" bson:"name"`
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
@@ -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()
|
||||
@@ -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
|
||||
}
|
||||
@@ -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{}
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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=
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package utils
|
||||
|
||||
type Consumer[T any] func(T)
|
||||
type Supplier[T any] func() T
|
||||
Reference in new issue
Block a user