im-kulikov/helium

View on GitHub
helium.go

Summary

Maintainability
A
0 mins
Test Coverage
package helium

import (
    "context"
    "fmt"
    stdlog "log"
    "os"
    "reflect"
    "strings"

    "go.uber.org/atomic"
    "go.uber.org/dig"
    "go.uber.org/zap"

    "github.com/im-kulikov/helium/logger"
    "github.com/im-kulikov/helium/module"
    "github.com/im-kulikov/helium/settings"
)

type (
    // App implementation for helium.
    App interface {
        Run(ctx context.Context) error
    }

    // Helium struct.
    Helium struct {
        di *dig.Container
    }

    // Settings struct.
    Settings struct {
        File         string
        Type         string
        Name         string
        Prefix       string
        BuildTime    string
        BuildVersion string
        Defaults     settings.Defaults
    }
)

var (
    // nolint:gochecknoglobals
    appName = atomic.NewString("helium")

    // nolint:gochecknoglobals
    appVersion = atomic.NewString("dev")
)

// New helium instance.
func New(cfg *Settings, mod ...module.Module) (*Helium, error) {
    h := &Helium{di: dig.New()}

    modules := module.Combine(mod...)

    if cfg != nil {
        if cfg.Prefix == "" {
            cfg.Prefix = cfg.Name
        }

        cfg.Prefix = strings.ToUpper(cfg.Prefix)

        if tmp := os.Getenv(cfg.Prefix + "_CONFIG"); tmp != "" {
            cfg.File = tmp
        }

        if tmp := os.Getenv(cfg.Prefix + "_CONFIG_TYPE"); tmp != "" {
            cfg.Type = tmp
        }

        core := settings.Core{
            File:         cfg.File,
            Type:         cfg.Type,
            Name:         cfg.Name,
            Prefix:       cfg.Prefix,
            BuildTime:    cfg.BuildTime,
            BuildVersion: cfg.BuildVersion,
        }

        appName.Store(cfg.Name)
        appVersion.Store(cfg.BuildVersion)

        modules = append(modules, core.Provider())
        modules = append(modules, settings.DIProvider(h.di))
    }

    if err := module.Provide(h.di, modules); err != nil {
        return nil, err
    }

    if cfg == nil || cfg.Defaults == nil {
        return h, nil
    }

    return h, h.di.Invoke(cfg.Defaults)
}

// Invoke dependencies from DI container.
func (h Helium) Invoke(fn interface{}, args ...dig.InvokeOption) error {
    return h.di.Invoke(fn, args...)
}

// Run trying invoke app instance from DI container and start app with Run call.
func (h Helium) Run() error {
    return h.di.Invoke(func(ctx context.Context, app App) error {
        return app.Run(ctx)
    })
}

// Catch errors.
func Catch(err error) {
    if err == nil {
        return
    }

    log, logErr := logger.
        NewLogger(logger.NewLoggerConfig(settings.Viper()), &settings.Core{
            Name:         appName.Load(),
            BuildVersion: appVersion.Load(),
        })
    if logErr != nil {
        stdlog.Fatal(err)
    } else {
        log.Fatal("Can't run app",
            zap.Error(err))
    }
}

// CatchTrace catch errors for debugging use that function just for debug your application.
func CatchTrace(err error) {
    if err == nil {
        return
    }

    // digging into the root of the problem
loop:
    for {
        var (
            ok bool
            v  = reflect.ValueOf(err)
            fn reflect.Value
        )

        switch {
        case v.Type().Kind() != reflect.Struct,
            !v.FieldByName("Reason").IsValid():
            break loop
        case v.FieldByName("Func").IsValid():
            fn = v.FieldByName("Func")
        }

        // nolint:forbidigo
        fmt.Printf("Place: %#v\nReason: %s\n\n", fn, err)

        if err, ok = v.FieldByName("Reason").Interface().(error); ok {
            continue
        }

        if err, ok = v.Interface().(error); ok {
            break
        }
    }

    panic(err)
}