This commit is contained in:
Alex Lushpai 2019-09-04 15:22:27 +03:00
commit 34cba277b9
17 changed files with 1559 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.idea

3
README.md Normal file
View File

@ -0,0 +1,3 @@
## MG Transport Library
This library provides different functions like error-reporting, loggingm localization, etc. in order to make it easier to create transports

169
core/config.go Normal file
View File

@ -0,0 +1,169 @@
package core
import (
"io/ioutil"
"path/filepath"
"github.com/op/go-logging"
"gopkg.in/yaml.v2"
)
// ConfigInterface settings data structure
type ConfigInterface interface {
GetVersion() string
GetSentryDSN() string
GetLogLevel() logging.Level
GetDebug() bool
GetHTTPConfig() HTTPServerConfig
GetDBConfig() DatabaseConfig
GetAWSConfig() ConfigAWS
GetTransportInfo() InfoInterface
GetUpdateInterval() int
IsDebug() bool
}
// InfoInterface transport settings data structure
type InfoInterface interface {
GetName() string
GetCode() string
GetLogoPath() string
}
// Config struct
type Config struct {
Version string `yaml:"version"`
LogLevel logging.Level `yaml:"log_level"`
Database DatabaseConfig `yaml:"database"`
SentryDSN string `yaml:"sentry_dsn"`
HTTPServer HTTPServerConfig `yaml:"http_server"`
Debug bool `yaml:"debug"`
UpdateInterval int `yaml:"update_interval"`
ConfigAWS ConfigAWS `yaml:"config_aws"`
TransportInfo Info `yaml:"transport_info"`
}
// Info struct
type Info struct {
Name string `yaml:"name"`
Code string `yaml:"code"`
LogoPath string `yaml:"logo_path"`
}
// ConfigAWS struct
type ConfigAWS struct {
AccessKeyID string `yaml:"access_key_id"`
SecretAccessKey string `yaml:"secret_access_key"`
Region string `yaml:"region"`
Bucket string `yaml:"bucket"`
FolderName string `yaml:"folder_name"`
ContentType string `yaml:"content_type"`
}
// DatabaseConfig struct
type DatabaseConfig struct {
Connection string `yaml:"connection"`
Logging bool `yaml:"logging"`
TablePrefix string `yaml:"table_prefix"`
MaxOpenConnections int `yaml:"max_open_connections"`
MaxIdleConnections int `yaml:"max_idle_connections"`
ConnectionLifetime int `yaml:"connection_lifetime"`
}
// HTTPServerConfig struct
type HTTPServerConfig struct {
Host string `yaml:"host"`
Listen string `yaml:"listen"`
}
// NewConfig reads configuration file and returns config instance
// Usage:
// NewConfig("config.yml")
func NewConfig(path string) *Config {
return (&Config{}).LoadConfig(path)
}
// LoadConfig read & load configuration file
func (c *Config) LoadConfig(path string) *Config {
var err error
path, err = filepath.Abs(path)
if err != nil {
panic(err)
}
source, err := ioutil.ReadFile(path)
if err != nil {
panic(err)
}
if err = yaml.Unmarshal(source, c); err != nil {
panic(err)
}
return c
}
// GetSentryDSN sentry connection dsn
func (c Config) GetSentryDSN() string {
return c.SentryDSN
}
// GetVersion transport version
func (c Config) GetVersion() string {
return c.Version
}
// GetLogLevel log level
func (c Config) GetLogLevel() logging.Level {
return c.LogLevel
}
// GetTransportInfo transport basic data
func (c Config) GetTransportInfo() InfoInterface {
return c.TransportInfo
}
// GetDebug debug flag
func (c Config) GetDebug() bool {
return c.Debug
}
// GetAWSConfig AWS configuration
func (c Config) GetAWSConfig() ConfigAWS {
return c.ConfigAWS
}
// GetDBConfig database configuration
func (c Config) GetDBConfig() DatabaseConfig {
return c.Database
}
// GetHTTPConfig server configuration
func (c Config) GetHTTPConfig() HTTPServerConfig {
return c.HTTPServer
}
// GetUpdateInterval user data update interval
func (c Config) GetUpdateInterval() int {
return c.UpdateInterval
}
// IsDebug debug state
func (c Config) IsDebug() bool {
return c.Debug
}
// GetName transport name
func (t Info) GetName() string {
return t.Name
}
// GetCode transport code
func (t Info) GetCode() string {
return t.Code
}
// GetLogoPath transport logo
func (t Info) GetLogoPath() string {
return t.LogoPath
}

115
core/engine.go Normal file
View File

@ -0,0 +1,115 @@
package core
import (
"github.com/gin-gonic/gin"
"github.com/op/go-logging"
)
// Engine struct
type Engine struct {
Localizer
ORM
Sentry
Utils
ginEngine *gin.Engine
Logger *logging.Logger
Config ConfigInterface
LogFormatter logging.Formatter
prepared bool
}
// New Engine instance (must be configured manually, gin can be accessed via engine.Router() directly or engine.ConfigureRouter(...) with callback)
func New() *Engine {
return &Engine{
Config: nil,
Localizer: Localizer{},
ORM: ORM{},
Sentry: Sentry{},
Utils: Utils{},
ginEngine: nil,
Logger: nil,
prepared: false,
}
}
func (e *Engine) initGin() {
if !e.Config.IsDebug() {
gin.SetMode(gin.ReleaseMode)
}
r := gin.New()
r.Use(gin.Recovery())
if e.Config.IsDebug() {
r.Use(gin.Logger())
}
r.Use(e.LocalizationMiddleware(), e.ErrorMiddleware())
e.ginEngine = r
}
// Prepare engine for start
func (e *Engine) Prepare() *Engine {
if e.prepared {
panic("engine already initialized")
}
if e.Config == nil {
panic("engine.Config must be loaded before initializing")
}
if e.DefaultError == "" {
e.DefaultError = "error"
}
if e.LogFormatter == nil {
e.LogFormatter = DefaultLogFormatter()
}
if e.LocaleBundle == nil {
e.LocaleBundle = DefaultLocalizerBundle()
}
if e.LocaleMatcher == nil {
e.LocaleMatcher = DefaultLocalizerMatcher()
}
e.LoadTranslations()
e.createDB(e.Config.GetDBConfig())
e.createRavenClient(e.Config.GetSentryDSN())
e.resetUtils(e.Config.GetAWSConfig(), e.Config.IsDebug(), 0)
e.Logger = NewLogger(e.Config.GetTransportInfo().GetCode(), e.Config.GetLogLevel(), e.LogFormatter)
e.Utils.Localizer = &e.Localizer
e.Sentry.Localizer = &e.Localizer
e.Utils.Logger = e.Logger
e.Sentry.Logger = e.Logger
e.prepared = true
return e
}
// CreateRenderer with translation function
func (e *Engine) CreateRenderer(callback func(*Renderer)) Renderer {
renderer := NewRenderer(e.LocalizationFuncMap())
callback(&renderer)
return renderer
}
// Router will return current gin.Engine or panic if it's not present
func (e *Engine) Router() *gin.Engine {
if !e.prepared {
panic("prepare engine first")
}
if e.ginEngine == nil {
e.initGin()
}
return e.ginEngine
}
// ConfigureRouter will call provided callback with current gin.Engine, or panic if engine is not present
func (e *Engine) ConfigureRouter(callback func(*gin.Engine)) *Engine {
callback(e.Router())
return e
}
// Run gin.Engine loop, or panic if engine is not present
func (e *Engine) Run() error {
return e.Router().Run(e.Config.GetHTTPConfig().Listen)
}

17
core/error.go Normal file
View File

@ -0,0 +1,17 @@
package core
import "net/http"
// ErrorResponse struct
type ErrorResponse struct {
Error string `json:"error"`
}
// BadRequest returns ErrorResponse with code 400
// Usage (with gin):
// context.JSON(BadRequest("invalid data"))
func BadRequest(error string) (int, interface{}) {
return http.StatusBadRequest, ErrorResponse{
Error: error,
}
}

137
core/localizer.go Normal file
View File

@ -0,0 +1,137 @@
package core
import (
"html/template"
"io/ioutil"
"path"
"github.com/gin-gonic/gin"
"github.com/nicksnyder/go-i18n/v2/i18n"
"golang.org/x/text/language"
"gopkg.in/yaml.v2"
)
// Localizer struct
type Localizer struct {
i18n *i18n.Localizer
LocaleBundle *i18n.Bundle
LocaleMatcher language.Matcher
LanguageTag language.Tag
TranslationsPath string
}
// NewLocalizer returns localizer instance with specified parameters.
// Usage:
// NewLocalizer(language.English, DefaultLocalizerBundle(), DefaultLocalizerMatcher(), "translations")
func NewLocalizer(locale language.Tag, bundle *i18n.Bundle, matcher language.Matcher, translationsPath string) *Localizer {
localizer := &Localizer{
i18n: nil,
LocaleBundle: bundle,
LocaleMatcher: matcher,
TranslationsPath: translationsPath,
}
localizer.SetLanguage(locale)
localizer.LoadTranslations()
return localizer
}
// DefaultLocalizerBundle returns new localizer bundle with English as default language
func DefaultLocalizerBundle() *i18n.Bundle {
return &i18n.Bundle{DefaultLanguage: language.English}
}
// DefaultLocalizerMatcher returns matcher with English, Russian and Spanish tags
func DefaultLocalizerMatcher() language.Matcher {
return language.NewMatcher([]language.Tag{
language.English,
language.Russian,
language.Spanish,
})
}
// LocalizationMiddleware returns gin.HandlerFunc which will set localizer language by Accept-Language header
// Usage:
// engine := gin.New()
// localizer := NewLocalizer("en", DefaultLocalizerBundle(), DefaultLocalizerMatcher(), "translations")
// engine.Use(localizer.LocalizationMiddleware())
func (l *Localizer) LocalizationMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
l.SetLocale(c.GetHeader("Accept-Language"))
}
}
// LocalizationFuncMap returns template.FuncMap (html template is used) with one method - trans
// Usage in code:
// engine := gin.New()
// engine.FuncMap = localizer.LocalizationFuncMap()
// or (with multitemplate)
// renderer := multitemplate.NewRenderer()
// funcMap := localizer.LocalizationFuncMap()
// renderer.AddFromFilesFuncs("index", funcMap, "template/index.html")
// funcMap must be passed for every .AddFromFilesFuncs call
// Usage in templates:
// <p class="info">{{"need_login_msg" | trans}}
// You can borrow FuncMap from this method and add your functions to it.
func (l *Localizer) LocalizationFuncMap() template.FuncMap {
return template.FuncMap{
"trans": l.GetLocalizedMessage,
}
}
func (l *Localizer) getLocaleBundle() *i18n.Bundle {
if l.LocaleBundle == nil {
l.LocaleBundle = DefaultLocalizerBundle()
}
return l.LocaleBundle
}
// LoadTranslations will load all translation files from translations directory
func (l *Localizer) LoadTranslations() {
l.getLocaleBundle().RegisterUnmarshalFunc("yml", yaml.Unmarshal)
files, err := ioutil.ReadDir(l.TranslationsPath)
if err != nil {
panic(err)
}
for _, f := range files {
if !f.IsDir() {
l.getLocaleBundle().MustLoadMessageFile(path.Join(l.TranslationsPath, f.Name()))
}
}
}
// SetLocale will change language for current localizer
func (l *Localizer) SetLocale(al string) {
tag, _ := language.MatchStrings(l.LocaleMatcher, al)
l.SetLanguage(tag)
}
// SetLanguage will change language using language tag
func (l *Localizer) SetLanguage(tag language.Tag) {
l.LanguageTag = tag
l.FetchLanguage()
}
// FetchLanguage will load language from tag
func (l *Localizer) FetchLanguage() {
l.i18n = i18n.NewLocalizer(l.getLocaleBundle(), l.LanguageTag.String())
}
// GetLocalizedMessage will return localized message by it's ID
func (l *Localizer) GetLocalizedMessage(messageID string) string {
return l.i18n.MustLocalize(&i18n.LocalizeConfig{MessageID: messageID})
}
// GetLocalizedTemplateMessage will return localized message with specified data
// It uses text/template syntax: https://golang.org/pkg/text/template/
func (l *Localizer) GetLocalizedTemplateMessage(messageID string, templateData map[string]interface{}) string {
return l.i18n.MustLocalize(&i18n.LocalizeConfig{
MessageID: messageID,
TemplateData: templateData,
})
}
// BadRequestLocalized is same as BadRequest(string), but passed string will be localized
func (l *Localizer) BadRequestLocalized(err string) (int, interface{}) {
return BadRequest(l.GetLocalizedMessage(err))
}

28
core/logger.go Normal file
View File

@ -0,0 +1,28 @@
package core
import (
"os"
"github.com/op/go-logging"
)
// NewLogger will create new logger with specified formatter.
// Usage:
// logger := NewLogger(config, DefaultLogFormatter())
func NewLogger(transportCode string, logLevel logging.Level, logFormat logging.Formatter) *logging.Logger {
logger := logging.MustGetLogger(transportCode)
logBackend := logging.NewLogBackend(os.Stdout, "", 0)
formatBackend := logging.NewBackendFormatter(logBackend, logFormat)
backend1Leveled := logging.AddModuleLevel(logBackend)
backend1Leveled.SetLevel(logLevel, "")
logging.SetBackend(formatBackend)
return logger
}
// DefaultLogFormatter will return default formatter for logs
func DefaultLogFormatter() logging.Formatter {
return logging.MustStringFormatter(
`%{time:2006-01-02 15:04:05.000} %{level:.4s} => %{message}`,
)
}

53
core/models.go Normal file
View File

@ -0,0 +1,53 @@
package core
import "time"
// Connection model
type Connection struct {
ID int `gorm:"primary_key"`
ClientID string `gorm:"column:client_id;type:varchar(70);not null;unique" json:"clientId,omitempty"`
Key string `gorm:"column:api_key;type:varchar(100);not null" json:"api_key,omitempty" binding:"required,max=100"`
URL string `gorm:"column:api_url;type:varchar(255);not null" json:"api_url,omitempty" binding:"required,validatecrmurl,max=255"`
GateURL string `gorm:"column:mg_url;type:varchar(255);not null;" json:"mg_url,omitempty" binding:"max=255"`
GateToken string `gorm:"column:mg_token;type:varchar(100);not null;unique" json:"mg_token,omitempty" binding:"max=100"`
CreatedAt time.Time
UpdatedAt time.Time
Active bool `json:"active,omitempty"`
Accounts []Account `gorm:"foreignkey:ConnectionID"`
}
// Account model
type Account struct {
ID int `gorm:"primary_key"`
ConnectionID int `gorm:"column:connection_id" json:"connectionId,omitempty"`
Channel uint64 `gorm:"column:channel;not null;unique" json:"channel,omitempty"`
ChannelSettingsHash string `gorm:"column:channel_settings_hash type:varchar(70)" binding:"max=70"`
Name string `gorm:"column:name type:varchar(40)" json:"name,omitempty" binding:"max=40"`
Lang string `gorm:"column:lang type:varchar(2)" json:"lang,omitempty" binding:"max=2"`
CreatedAt time.Time
UpdatedAt time.Time
}
// User model
type User struct {
ID int `gorm:"primary_key"`
ExternalID int `gorm:"column:external_id;not null;unique"`
UserPhotoURL string `gorm:"column:user_photo_url type:varchar(255)" binding:"max=255"`
UserPhotoID string `gorm:"column:user_photo_id type:varchar(100)" binding:"max=100"`
CreatedAt time.Time
UpdatedAt time.Time
}
// TableName will return table name for User
// It will not work if User is not embedded, but mapped as another type
// type MyUser User // will not work
// but
// type MyUser struct { // will work
// User
// }
func (User) TableName() string {
return "mg_user"
}
// Accounts list
type Accounts []Account

42
core/orm.go Normal file
View File

@ -0,0 +1,42 @@
package core
import (
"time"
"github.com/jinzhu/gorm"
// PostgreSQL is an default
_ "github.com/jinzhu/gorm/dialects/postgres"
)
// ORM struct
type ORM struct {
DB *gorm.DB
}
// NewORM will init new database connection
func NewORM(config DatabaseConfig) *ORM {
orm := &ORM{}
orm.createDB(config)
return orm
}
func (orm *ORM) createDB(config DatabaseConfig) {
db, err := gorm.Open("postgres", config.Connection)
if err != nil {
panic(err)
}
db.DB().SetConnMaxLifetime(time.Duration(config.ConnectionLifetime) * time.Second)
db.DB().SetMaxOpenConns(config.MaxOpenConnections)
db.DB().SetMaxIdleConns(config.MaxIdleConnections)
db.SingularTable(true)
db.LogMode(config.Logging)
orm.DB = db
}
// CloseDB close database connection
func (orm *ORM) CloseDB() {
_ = orm.DB.Close()
}

382
core/sentry.go Normal file
View File

@ -0,0 +1,382 @@
package core
import (
"errors"
"fmt"
"net/http"
"reflect"
"runtime/debug"
"strconv"
"github.com/Neur0toxine/mg-transport-lib/internal"
"github.com/getsentry/raven-go"
"github.com/gin-gonic/gin"
"github.com/op/go-logging"
)
// ErrorHandlerFunc will handle errors
type ErrorHandlerFunc func(recovery interface{}, c *gin.Context)
// SentryTaggedTypes list
type SentryTaggedTypes []SentryTagged
// SentryTags list for SentryTaggedStruct. Format: name => property name
type SentryTags map[string]string
// SentryTagged interface for both tagged scalar and struct
type SentryTagged interface {
BuildTags(interface{}) (map[string]string, error)
GetContextKey() string
GetTags() SentryTags
GetName() string
}
// Sentry struct. Holds SentryTaggedStruct list
type Sentry struct {
TaggedTypes SentryTaggedTypes
Stacktrace bool
DefaultError string
Localizer *Localizer
Logger *logging.Logger
Client *raven.Client
}
// SentryTaggedStruct holds information about type, it's key in gin.Context (for middleware), and it's properties
type SentryTaggedStruct struct {
Type reflect.Type
GinContextKey string
Tags SentryTags
}
// SentryTaggedScalar variable from context
type SentryTaggedScalar struct {
SentryTaggedStruct
Name string
}
// NewSentry constructor
func NewSentry(sentryDSN string, defaultError string, taggedTypes SentryTaggedTypes, logger *logging.Logger, localizer *Localizer) *Sentry {
sentry := &Sentry{
DefaultError: defaultError,
TaggedTypes: taggedTypes,
Localizer: localizer,
Logger: logger,
Stacktrace: true,
}
sentry.createRavenClient(sentryDSN)
return sentry
}
// NewTaggedStruct constructor
func NewTaggedStruct(sample interface{}, ginCtxKey string, tags map[string]string) *SentryTaggedStruct {
n := make(map[string]string)
for k, v := range tags {
n[v] = k
}
return &SentryTaggedStruct{
Type: reflect.TypeOf(sample),
GinContextKey: ginCtxKey,
Tags: n,
}
}
// NewTaggedScalar constructor
func NewTaggedScalar(sample interface{}, ginCtxKey string, name string) *SentryTaggedScalar {
return &SentryTaggedScalar{
SentryTaggedStruct: SentryTaggedStruct{
Type: reflect.TypeOf(sample),
GinContextKey: ginCtxKey,
Tags: SentryTags{},
},
Name: name,
}
}
// createRavenClient will init raven.Client
func (s *Sentry) createRavenClient(sentryDSN string) {
client, _ := raven.New(sentryDSN)
s.Client = client
}
// combineGinErrorHandlers calls several error handlers simultaneously
func (s *Sentry) combineGinErrorHandlers(handlers ...ErrorHandlerFunc) gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
rec := recover()
for _, handler := range handlers {
handler(rec, c)
}
if rec != nil || len(c.Errors) > 0 {
c.Abort()
}
}()
c.Next()
}
}
// ErrorMiddleware returns error handlers, attachable to gin.Engine
func (s *Sentry) ErrorMiddleware() gin.HandlerFunc {
defaultHandlers := []ErrorHandlerFunc{
s.ErrorResponseHandler(),
s.PanicLogger(),
s.ErrorLogger(),
}
if s.Client != nil {
defaultHandlers = append(defaultHandlers, s.ErrorCaptureHandler())
}
return s.combineGinErrorHandlers(defaultHandlers...)
}
// PanicLogger logs panic
func (s *Sentry) PanicLogger() ErrorHandlerFunc {
return func(recovery interface{}, c *gin.Context) {
if recovery != nil {
if s.Logger != nil {
s.Logger.Error(c.Request.RequestURI, recovery)
} else {
fmt.Print("ERROR =>", c.Request.RequestURI, recovery)
}
debug.PrintStack()
}
}
}
// ErrorLogger logs basic errors
func (s *Sentry) ErrorLogger() ErrorHandlerFunc {
return func(recovery interface{}, c *gin.Context) {
for _, err := range c.Errors {
if s.Logger != nil {
s.Logger.Error(c.Request.RequestURI, err.Err)
} else {
fmt.Print("ERROR =>", c.Request.RequestURI, err.Err)
}
}
}
}
// ErrorResponseHandler will be executed in case of any unexpected error
func (s *Sentry) ErrorResponseHandler() ErrorHandlerFunc {
return func(recovery interface{}, c *gin.Context) {
publicErrors := c.Errors.ByType(gin.ErrorTypePublic)
privateLen := len(c.Errors.ByType(gin.ErrorTypePrivate))
publicLen := len(publicErrors)
if privateLen == 0 && publicLen == 0 && recovery == nil {
return
}
messagesLen := publicLen
if privateLen > 0 || recovery != nil {
messagesLen++
}
messages := make([]string, messagesLen)
index := 0
for _, err := range publicErrors {
messages[index] = err.Error()
index++
}
if privateLen > 0 || recovery != nil {
if s.Localizer == nil {
messages[index] = s.DefaultError
} else {
messages[index] = s.Localizer.GetLocalizedMessage(s.DefaultError)
}
}
c.JSON(http.StatusInternalServerError, gin.H{"error": messages})
}
}
// ErrorCaptureHandler will generate error data and send it to sentry
func (s *Sentry) ErrorCaptureHandler() ErrorHandlerFunc {
return func(recovery interface{}, c *gin.Context) {
tags := map[string]string{
"endpoint": c.Request.RequestURI,
}
if len(s.TaggedTypes) > 0 {
for _, tagged := range s.TaggedTypes {
if item, ok := c.Get(tagged.GetContextKey()); ok && item != nil {
if itemTags, err := tagged.BuildTags(item); err == nil {
for tagName, tagValue := range itemTags {
tags[tagName] = tagValue
}
}
}
}
}
if recovery != nil {
stacktrace := raven.NewStacktrace(4, 3, nil)
recStr := fmt.Sprint(recovery)
err := errors.New(recStr)
go s.Client.CaptureMessageAndWait(
recStr,
tags,
raven.NewException(err, stacktrace),
raven.NewHttp(c.Request),
)
}
for _, err := range c.Errors {
if s.Stacktrace {
stacktrace := internal.NewRavenStackTrace(s.Client, err.Err, 0)
go s.Client.CaptureMessageAndWait(
err.Error(),
tags,
raven.NewException(err.Err, stacktrace),
raven.NewHttp(c.Request),
)
} else {
go s.Client.CaptureErrorAndWait(err.Err, tags)
}
}
}
}
// AddTag will add tag with property name which holds tag in object
func (t *SentryTaggedStruct) AddTag(name string, property string) *SentryTaggedStruct {
t.Tags[property] = name
return t
}
// GetTags is Tags getter
func (t *SentryTaggedStruct) GetTags() SentryTags {
return t.Tags
}
// GetContextKey is GinContextKey getter
func (t *SentryTaggedStruct) GetContextKey() string {
return t.GinContextKey
}
// GetName is useless for SentryTaggedStruct
func (t *SentryTaggedStruct) GetName() string {
return ""
}
// GetProperty will extract property string representation from specified object. It will be properly formatted.
func (t *SentryTaggedStruct) GetProperty(v interface{}, property string) (name string, value string, err error) {
val := reflect.Indirect(reflect.ValueOf(v))
if !val.IsValid() {
err = errors.New("invalid value provided")
return
}
if val.Kind() != reflect.Struct {
err = fmt.Errorf("passed value must be struct, %s provided", val.String())
return
}
if val.Type().Name() != t.Type.Name() {
err = fmt.Errorf("passed value should be of type `%s`, got `%s` instead", t.Type.String(), val.Type().String())
return
}
if i, ok := t.Tags[property]; ok {
name = i
} else {
err = fmt.Errorf("cannot find property `%s`", property)
}
field := val.FieldByName(property)
if !field.IsValid() {
err = fmt.Errorf("invalid property, got %s", field.String())
return
}
value = t.valueToString(field)
return
}
// BuildTags will extract tags for Sentry from specified object
func (t *SentryTaggedStruct) BuildTags(v interface{}) (tags map[string]string, err error) {
items := make(map[string]string)
for prop, name := range t.Tags {
if _, value, e := t.GetProperty(v, prop); e == nil {
items[name] = value
} else {
err = e
return
}
}
tags = items
return
}
// valueToString convert reflect.Value to string representation
func (t *SentryTaggedStruct) valueToString(field reflect.Value) string {
k := field.Kind()
switch {
case k == reflect.Bool:
return strconv.FormatBool(field.Bool())
case k >= reflect.Int && k <= reflect.Int64:
return strconv.FormatInt(field.Int(), 10)
case k >= reflect.Uint && k <= reflect.Uintptr:
return strconv.FormatUint(field.Uint(), 10)
case k == reflect.Float32 || k == reflect.Float64:
bitSize := 32
if k == reflect.Float64 {
bitSize = 64
}
return strconv.FormatFloat(field.Float(), 'f', 12, bitSize)
default:
return field.String()
}
}
// GetTags is useless for SentryTaggedScalar
func (t *SentryTaggedScalar) GetTags() SentryTags {
return SentryTags{}
}
// GetContextKey is getter for GinContextKey
func (t *SentryTaggedScalar) GetContextKey() string {
return t.GinContextKey
}
// GetName is getter for Name (tag name for scalar)
func (t *SentryTaggedScalar) GetName() string {
return t.Name
}
// Get will extract property string representation from specified object. It will be properly formatted.
func (t *SentryTaggedScalar) Get(v interface{}) (value string, err error) {
val := reflect.Indirect(reflect.ValueOf(v))
if !val.IsValid() {
err = errors.New("invalid value provided")
return
}
if val.Kind() == reflect.Struct {
err = errors.New("passed value must not be struct")
return
}
if val.Type().Name() != t.Type.Name() {
err = fmt.Errorf("passed value should be of type `%s`, got `%s` instead", t.Type.String(), val.Type().String())
return
}
value = t.valueToString(val)
return
}
// BuildTags returns map with single item in this format: <tag name> => <scalar value>
func (t *SentryTaggedScalar) BuildTags(v interface{}) (items map[string]string, err error) {
items = make(map[string]string)
if value, e := t.Get(v); e == nil {
items[t.Name] = value
} else {
err = e
}
return
}

26
core/template.go Normal file
View File

@ -0,0 +1,26 @@
package core
import (
"html/template"
"github.com/gin-contrib/multitemplate"
)
// Renderer wraps multitemplate.Renderer in order to make it easier to use
type Renderer struct {
multitemplate.Renderer
FuncMap template.FuncMap
}
// NewRenderer is a Renderer constructor
func NewRenderer(funcMap template.FuncMap) Renderer {
return Renderer{
Renderer: multitemplate.NewRenderer(),
FuncMap: funcMap,
}
}
// Push is an AddFromFilesFuncs wrapper
func (r *Renderer) Push(name string, files ...string) *template.Template {
return r.AddFromFilesFuncs(name, r.FuncMap, files...)
}

208
core/utils.go Normal file
View File

@ -0,0 +1,208 @@
package core
import (
"crypto/sha1"
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
"net/http"
"regexp"
"strings"
"sync/atomic"
"time"
"github.com/Neur0toxine/mg-transport-lib/internal"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/op/go-logging"
v5 "github.com/retailcrm/api-client-go/v5"
v1 "github.com/retailcrm/mg-transport-api-client-go/v1"
)
// Utils service object
type Utils struct {
IsDebug bool
ConfigAWS ConfigAWS
Localizer *Localizer
Logger *logging.Logger
TokenCounter uint32
slashRegex *regexp.Regexp
}
// NewUtils will create new Utils instance
func NewUtils(awsConfig ConfigAWS, localizer *Localizer, logger *logging.Logger, debug bool) *Utils {
return &Utils{
IsDebug: debug,
ConfigAWS: awsConfig,
Localizer: localizer,
Logger: logger,
TokenCounter: 0,
slashRegex: internal.SlashRegex,
}
}
// resetUtils
func (u *Utils) resetUtils(awsConfig ConfigAWS, debug bool, tokenCounter uint32) {
u.TokenCounter = tokenCounter
u.ConfigAWS = awsConfig
u.IsDebug = debug
u.slashRegex = internal.SlashRegex
}
// GenerateToken will generate long pseudo-random string.
func (u *Utils) GenerateToken() string {
c := atomic.AddUint32(&u.TokenCounter, 1)
return fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprintf("%d%d", time.Now().UnixNano(), c))))
}
// GetAPIClient will initialize retailCRM api client from url and key
func (u *Utils) GetAPIClient(url, key string) (*v5.Client, int, error) {
client := v5.New(url, key)
client.Debug = u.IsDebug
cr, status, e := client.APICredentials()
if e.RuntimeErr != nil {
u.Logger.Error(url, status, e.RuntimeErr, cr)
return nil, http.StatusInternalServerError, e.RuntimeErr
}
if !cr.Success {
u.Logger.Error(url, status, e.ApiErr, cr)
return nil, http.StatusBadRequest, errors.New(u.Localizer.GetLocalizedMessage("incorrect_url_key"))
}
if res := u.checkCredentials(cr.Credentials); len(res) != 0 {
u.Logger.Error(url, status, res)
return nil,
http.StatusBadRequest,
errors.New(
u.Localizer.GetLocalizedTemplateMessage(
"missing_credentials",
map[string]interface{}{
"Credentials": strings.Join(res, ", "),
},
),
)
}
return client, 0, nil
}
func (u *Utils) checkCredentials(credential []string) []string {
rc := make([]string, len(internal.CredentialsTransport))
copy(rc, internal.CredentialsTransport)
for _, vc := range credential {
for kn, vn := range rc {
if vn == vc {
if len(rc) == 1 {
rc = rc[:0]
break
}
rc = append(rc[:kn], rc[kn+1:]...)
}
}
}
return rc
}
// UploadUserAvatar will upload avatar for user
func (u *Utils) UploadUserAvatar(url string) (picURLs3 string, err error) {
s3Config := &aws.Config{
Credentials: credentials.NewStaticCredentials(
u.ConfigAWS.AccessKeyID,
u.ConfigAWS.SecretAccessKey,
""),
Region: aws.String(u.ConfigAWS.Region),
}
s := session.Must(session.NewSession(s3Config))
uploader := s3manager.NewUploader(s)
resp, err := http.Get(url)
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode >= http.StatusBadRequest {
return "", fmt.Errorf("get: %v code: %v", url, resp.StatusCode)
}
result, err := uploader.Upload(&s3manager.UploadInput{
Bucket: aws.String(u.ConfigAWS.Bucket),
Key: aws.String(fmt.Sprintf("%v/%v.jpg", u.ConfigAWS.FolderName, u.GenerateToken())),
Body: resp.Body,
ContentType: aws.String(u.ConfigAWS.ContentType),
ACL: aws.String("public-read"),
})
if err != nil {
return
}
picURLs3 = result.Location
return
}
// GetMGItemData will upload file to MG by URL and return information about attachable item
func GetMGItemData(client *v1.MgClient, url string, caption string) (v1.Item, int, error) {
item := v1.Item{}
data, st, err := client.UploadFileByURL(
v1.UploadFileByUrlRequest{
Url: url,
},
)
if err != nil {
return item, st, err
}
item.ID = data.ID
item.Caption = caption
return item, st, err
}
// RemoveTrailingSlash will remove slash at the end of any string
func (u *Utils) RemoveTrailingSlash(crmURL string) string {
return u.slashRegex.ReplaceAllString(crmURL, ``)
}
// GetEntitySHA1 will serialize any value to JSON and return SHA1 hash of this JSON
func GetEntitySHA1(v interface{}) (hash string, err error) {
res, _ := json.Marshal(v)
h := sha1.New()
_, err = h.Write(res)
hash = fmt.Sprintf("%x", h.Sum(nil))
return
}
// ReplaceMarkdownSymbols will remove markdown symbols from text
func ReplaceMarkdownSymbols(s string) string {
for _, v := range internal.MarkdownSymbols {
s = strings.Replace(s, v, "\\"+v, -1)
}
return s
}
// DefaultCurrencies will return default currencies list for all bots
func DefaultCurrencies() map[string]string {
return map[string]string{
"rub": "₽",
"uah": "₴",
"byr": "Br",
"kzt": "₸",
"usd": "$",
"eur": "€",
}
}

24
core/validator.go Normal file
View File

@ -0,0 +1,24 @@
package core
import (
"reflect"
"github.com/gin-gonic/gin/binding"
"github.com/Neur0toxine/mg-transport-lib/internal"
"gopkg.in/go-playground/validator.v8"
)
func init() {
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
if err := v.RegisterValidation("validatecrmurl", validateCrmURL); err != nil {
panic("cannot register crm url validator: " + err.Error())
}
}
}
func validateCrmURL(
v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
) bool {
return internal.RegCommandName.Match([]byte(field.Interface().(string)))
}

35
go.mod Normal file
View File

@ -0,0 +1,35 @@
module github.com/retailcrm/mg-transport-core
go 1.12
require (
github.com/aws/aws-sdk-go v1.23.9
github.com/certifi/gocertifi v0.0.0-20190506164543-d2eda7129713 // indirect
github.com/denisenkom/go-mssqldb v0.0.0-20190830225923-3302f0226fbd // indirect
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect
github.com/getsentry/raven-go v0.0.0-20180903072508-084a9de9eb03
github.com/gin-contrib/multitemplate v0.0.0-20180827023943-5799bbbb6dce
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.3.0
github.com/go-sql-driver/mysql v1.4.1 // indirect
github.com/golang/protobuf v1.3.2 // indirect
github.com/google/go-querystring v1.0.0 // indirect
github.com/jinzhu/gorm v1.9.1
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.0.1 // indirect
github.com/lib/pq v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.9 // indirect
github.com/mattn/go-sqlite3 v1.11.0 // indirect
github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.5
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pkg/errors v0.8.1
github.com/retailcrm/api-client-go v1.1.1
github.com/retailcrm/mg-transport-api-client-go v1.1.31
github.com/stretchr/testify v1.4.0 // indirect
github.com/ugorji/go v1.1.7 // indirect
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2
golang.org/x/tools v0.0.0-20190830082254-f340ed3ae274 // indirect
gopkg.in/go-playground/validator.v8 v8.18.2
gopkg.in/yaml.v2 v2.2.2
)

200
go.sum Normal file
View File

@ -0,0 +1,200 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
github.com/BurntSushi/toml v0.3.0 h1:e1/Ivsx3Z0FVTV0NSOv/aVgbUWyQuzj7DDnFblkRvsY=
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/aws/aws-sdk-go v1.23.9 h1:UYWPGrBMlrW5VCYeWMbog1T/kqZzkvvheUDQaaUAhqI=
github.com/aws/aws-sdk-go v1.23.9/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/certifi/gocertifi v0.0.0-20190506164543-d2eda7129713 h1:UNOqI3EKhvbqV8f1Vm3NIwkrhq388sGCeAH2Op7w0rc=
github.com/certifi/gocertifi v0.0.0-20190506164543-d2eda7129713/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/denisenkom/go-mssqldb v0.0.0-20190830225923-3302f0226fbd h1:DoaaxHqzWPQCWKSTmsi8UDSiFqxbfue+Xt+qi/BFKb8=
github.com/denisenkom/go-mssqldb v0.0.0-20190830225923-3302f0226fbd/go.mod h1:uU0N10vx1abI4qeVe79CxepBP6PPREVTgMS5Gx6/mOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/getsentry/raven-go v0.0.0-20180903072508-084a9de9eb03 h1:G/9fPivTr5EiyqE9OlW65iMRUxFXMGRHgZFGo50uG8Q=
github.com/getsentry/raven-go v0.0.0-20180903072508-084a9de9eb03/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/gin-contrib/multitemplate v0.0.0-20180827023943-5799bbbb6dce h1:KqeVCdb+M2iwyF6GzdYxTazfE1cE+133RXuGaZ5Sc1E=
github.com/gin-contrib/multitemplate v0.0.0-20180827023943-5799bbbb6dce/go.mod h1:62qM8p4crGvNKE413gTzn4eMFin1VOJfMDWMRzHdvqM=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.3.0 h1:kCmZyPklC0gVdL728E6Aj20uYBJV93nj/TkwBTKhFbs=
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/subcommands v1.0.1 h1:/eqq+otEXm5vhfBrbREPCSVQbvofip6kIz+mX5TUH7k=
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/wire v0.3.0 h1:imGQZGEVEHpje5056+K+cgdO72p0LQv2xIIFXNGUf60=
github.com/google/wire v0.3.0/go.mod h1:i1DMg/Lu8Sz5yYl25iOdmc5CT5qusaa+zmRWs16741s=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jinzhu/gorm v1.9.1 h1:lDSDtsCt5AGGSKTs8AHlSDbbgif4G4+CKJ8ETBDVHTA=
github.com/jinzhu/gorm v1.9.1/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.5 h1:/TjjTS4kg7vC+05gD0LE4+97f/+PRFICnK/7wJPk7kE=
github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.5/go.mod h1:4Opqa6/HIv0lhG3WRAkqzO0afezkRhxXI0P8EJkqeRU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/retailcrm/api-client-go v1.1.1 h1:yqsyYjBDdmDwExVlTdGucY9/IpEokXpkfTfA6z5AZ7M=
github.com/retailcrm/api-client-go v1.1.1/go.mod h1:QRoPE2SM6ST7i2g0yEdqm7Iw98y7cYuq3q14Ot+6N8c=
github.com/retailcrm/mg-transport-api-client-go v1.1.31 h1:21pE1JhT49rvbMLDYJa0iiqbb/roz+eSp27fPck4uUw=
github.com/retailcrm/mg-transport-api-client-go v1.1.31/go.mod h1:AWV6BueE28/6SCoyfKURTo4lF0oXYoOKmHTzehd5vAI=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20171214130843-f21a4dfb5e38/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190830082254-f340ed3ae274 h1:3LEbAKuShoQDlrpbepJOeKph85ROShka+GypY1YNQYQ=
golang.org/x/tools v0.0.0-20190830082254-f340ed3ae274/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

105
internal/stacktrace.go Normal file
View File

@ -0,0 +1,105 @@
package internal
import (
"runtime"
"github.com/getsentry/raven-go"
"github.com/pkg/errors"
)
// NewRavenStackTrace generate stacktrace compatible with raven-go format
// It tries to extract better stacktrace from error from package "github.com/pkg/errors"
// In case of fail it will fallback to default stacktrace generation from raven-go.
// Default stacktrace highly likely will be useless, because it will not include call
// which returned error. This occurs because default stacktrace doesn't include any call
// before stacktrace generation, and raven-go will generate stacktrace here, which will end
// in trace to this file. But errors from "github.com/pkg/errors" will generate stacktrace
// immediately, it will include call which returned error, and we can fetch this trace.
// Also we can wrap default errors with error from this package, like this:
// errors.Wrap(err, err.Error)
func NewRavenStackTrace(client *raven.Client, myerr error, skip int) *raven.Stacktrace {
st := getErrorStackTraceConverted(myerr, 3, client.IncludePaths())
if st == nil {
st = raven.NewStacktrace(skip, 3, client.IncludePaths())
}
return st
}
// getErrorStackTraceConverted will return converted stacktrace from custom error, or nil in case of default error
func getErrorStackTraceConverted(err error, context int, appPackagePrefixes []string) *raven.Stacktrace {
st := getErrorCauseStackTrace(err)
if st == nil {
return nil
}
return convertStackTrace(st, context, appPackagePrefixes)
}
// getErrorCauseStackTrace tries to extract stacktrace from custom error, returns nil in case of failure
func getErrorCauseStackTrace(err error) errors.StackTrace {
// This code is inspired by github.com/pkg/errors.Cause().
var st errors.StackTrace
for err != nil {
s := getErrorStackTrace(err)
if s != nil {
st = s
}
err = getErrorCause(err)
}
return st
}
// convertStackTrace converts github.com/pkg/errors.StackTrace to github.com/getsentry/raven-go.Stacktrace
func convertStackTrace(st errors.StackTrace, context int, appPackagePrefixes []string) *raven.Stacktrace {
// This code is borrowed from github.com/getsentry/raven-go.NewStacktrace().
var frames []*raven.StacktraceFrame
for _, f := range st {
frame := convertFrame(f, context, appPackagePrefixes)
if frame != nil {
frames = append(frames, frame)
}
}
if len(frames) == 0 {
return nil
}
for i, j := 0, len(frames)-1; i < j; i, j = i+1, j-1 {
frames[i], frames[j] = frames[j], frames[i]
}
return &raven.Stacktrace{Frames: frames}
}
// convertFrame converts single frame from github.com/pkg/errors.Frame to github.com/pkg/errors.Frame
func convertFrame(f errors.Frame, context int, appPackagePrefixes []string) *raven.StacktraceFrame {
// This code is borrowed from github.com/pkg/errors.Frame.
pc := uintptr(f) - 1
fn := runtime.FuncForPC(pc)
var file string
var line int
if fn != nil {
file, line = fn.FileLine(pc)
} else {
file = "unknown"
}
return raven.NewStacktraceFrame(pc, file, line, context, appPackagePrefixes)
}
// getErrorStackTrace will try to extract stacktrace from error using StackTrace method (default errors doesn't have it)
func getErrorStackTrace(err error) errors.StackTrace {
ster, ok := err.(interface {
StackTrace() errors.StackTrace
})
if !ok {
return nil
}
return ster.StackTrace()
}
// getErrorCause will try to extract original error from wrapper - it is used only if stacktrace is not present
func getErrorCause(err error) error {
cer, ok := err.(interface {
Cause() error
})
if !ok {
return nil
}
return cer.Cause()
}

14
internal/variables.go Normal file
View File

@ -0,0 +1,14 @@
package internal
import "regexp"
// CredentialsTransport set of API methods for transport registration
var (
CredentialsTransport = []string{
"/api/integration-modules/{code}",
"/api/integration-modules/{code}/edit",
}
MarkdownSymbols = []string{"*", "_", "`", "["}
RegCommandName = regexp.MustCompile(`^https://?[\da-z.-]+\.(retailcrm\.(ru|pro|es)|ecomlogic\.com|simlachat\.(com|ru))/?$`)
SlashRegex = regexp.MustCompile(`/+$`)
)