Merge pull request #10 from Neur0toxine/master

updated gin to v1.5.0, several other changes
This commit is contained in:
Alex Lushpai 2019-12-13 16:17:22 +03:00 committed by GitHub
commit f42723ab28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 681 additions and 157 deletions

21
LICENSE.md Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2019 RetailDriver LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -2,6 +2,8 @@
[![Build Status](https://travis-ci.org/retailcrm/mg-transport-core.svg?branch=master)](https://travis-ci.org/retailcrm/mg-transport-core) [![Build Status](https://travis-ci.org/retailcrm/mg-transport-core.svg?branch=master)](https://travis-ci.org/retailcrm/mg-transport-core)
[![codecov](https://codecov.io/gh/retailcrm/mg-transport-core/branch/master/graph/badge.svg)](https://codecov.io/gh/retailcrm/mg-transport-core) [![codecov](https://codecov.io/gh/retailcrm/mg-transport-core/branch/master/graph/badge.svg)](https://codecov.io/gh/retailcrm/mg-transport-core)
[![GoDoc](https://godoc.org/github.com/retailcrm/mg-transport-core/core?status.svg)](https://godoc.org/github.com/retailcrm/mg-transport-core/core) [![GoDoc](https://godoc.org/github.com/retailcrm/mg-transport-core/core?status.svg)](https://godoc.org/github.com/retailcrm/mg-transport-core/core)
[![Go Report Card](https://goreportcard.com/badge/github.com/retailcrm/mg-transport-core)](https://goreportcard.com/report/github.com/retailcrm/mg-transport-core)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/retailcrm/mg-transport-core/blob/master/LICENSE.md)
This library provides different functions like error-reporting, logging, localization, etc. in order to make it easier to create transports. This library provides different functions like error-reporting, logging, localization, etc. in order to make it easier to create transports.
Usage: Usage:
```go ```go

View File

@ -7,6 +7,7 @@ import (
"github.com/retailcrm/mg-transport-core/core" "github.com/retailcrm/mg-transport-core/core"
) )
// Options for tool command
type Options struct{} type Options struct{}
var ( var (

View File

@ -16,7 +16,8 @@ var (
"/api/integration-modules/{code}/edit", "/api/integration-modules/{code}/edit",
} }
markdownSymbols = []string{"*", "_", "`", "["} markdownSymbols = []string{"*", "_", "`", "["}
regCommandName = regexp.MustCompile(`^https://?[\da-z.-]+\.(retailcrm\.(ru|pro|es)|ecomlogic\.com|simlachat\.(com|ru))/?$`) regCommandName = regexp.MustCompile(
`^https://?[\da-z.-]+\.(retailcrm\.(ru|pro|es)|ecomlogic\.com|simlachat\.(com|ru))/?$`)
slashRegex = regexp.MustCompile(`/+$`) slashRegex = regexp.MustCompile(`/+$`)
) )
@ -85,7 +86,7 @@ type DatabaseConfig struct {
// HTTPClientConfig struct // HTTPClientConfig struct
type HTTPClientConfig struct { type HTTPClientConfig struct {
Timeout time.Duration `yaml:"timeout"` Timeout time.Duration `yaml:"timeout"`
SSLVerification bool `yaml:"ssl_verification"` SSLVerification *bool `yaml:"ssl_verification"`
MockAddress string `yaml:"mock_address"` MockAddress string `yaml:"mock_address"`
MockedDomains []string `yaml:"mocked_domains"` MockedDomains []string `yaml:"mocked_domains"`
} }
@ -198,3 +199,12 @@ func (t Info) GetCode() string {
func (t Info) GetLogoPath() string { func (t Info) GetLogoPath() string {
return t.LogoPath return t.LogoPath
} }
// IsSSLVerificationEnabled returns SSL verification flag (default is true)
func (h *HTTPClientConfig) IsSSLVerificationEnabled() bool {
if h.SSLVerification == nil {
return true
}
return *h.SSLVerification
}

View File

@ -41,6 +41,10 @@ log_level: 5
debug: true debug: true
update_interval: 24 update_interval: 24
http_client:
ssl_verification: false
timeout: 30
config_aws: config_aws:
access_key_id: key access_key_id: key
secret_access_key: secret secret_access_key: secret

View File

@ -2,6 +2,7 @@ package core
import ( import (
"bytes" "bytes"
// nolint:gosec
"crypto/sha1" "crypto/sha1"
"encoding/base64" "encoding/base64"
"io" "io"
@ -14,8 +15,31 @@ import (
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
) )
// CSRFErrorReason is a error reason type
type CSRFErrorReason uint8
// CSRFTokenGetter func type // CSRFTokenGetter func type
type CSRFTokenGetter func(c *gin.Context) string type CSRFTokenGetter func(*gin.Context) string
// CSRFAbortFunc is a callback which
type CSRFAbortFunc func(*gin.Context, CSRFErrorReason)
const (
// CSRFErrorNoTokenInSession will be returned if token is not present in session
CSRFErrorNoTokenInSession CSRFErrorReason = iota
// CSRFErrorCannotStoreTokenInSession will be returned if middleware cannot store token in session
CSRFErrorCannotStoreTokenInSession
// CSRFErrorIncorrectTokenType will be returned if data type of token in session is not string
CSRFErrorIncorrectTokenType
// CSRFErrorEmptyToken will be returned if token in session is empty
CSRFErrorEmptyToken
// CSRFErrorTokenMismatch will be returned in case of invalid token
CSRFErrorTokenMismatch
)
// DefaultCSRFTokenGetter default getter // DefaultCSRFTokenGetter default getter
var DefaultCSRFTokenGetter = func(c *gin.Context) string { var DefaultCSRFTokenGetter = func(c *gin.Context) string {
@ -44,14 +68,14 @@ var DefaultCSRFTokenGetter = func(c *gin.Context) string {
// DefaultIgnoredMethods ignored methods for CSRF verifier middleware // DefaultIgnoredMethods ignored methods for CSRF verifier middleware
var DefaultIgnoredMethods = []string{"GET", "HEAD", "OPTIONS"} var DefaultIgnoredMethods = []string{"GET", "HEAD", "OPTIONS"}
// CSRF struct. Provides CSRF token verification.
type CSRF struct { type CSRF struct {
salt string salt string
secret string secret string
sessionName string sessionName string
abortFunc gin.HandlerFunc abortFunc CSRFAbortFunc
csrfTokenGetter CSRFTokenGetter csrfTokenGetter CSRFTokenGetter
store sessions.Store store sessions.Store
locale *Localizer
} }
// NewCSRF creates CSRF struct with specified configuration and session store. // NewCSRF creates CSRF struct with specified configuration and session store.
@ -59,7 +83,7 @@ type CSRF struct {
// Salt must be different every time (pass empty salt to use random), secret must be provided, sessionName is optional - pass empty to use default, // Salt must be different every time (pass empty salt to use random), secret must be provided, sessionName is optional - pass empty to use default,
// store will be used to store sessions, abortFunc will be called to return error if token is invalid, csrfTokenGetter will be used to obtain token. // store will be used to store sessions, abortFunc will be called to return error if token is invalid, csrfTokenGetter will be used to obtain token.
// Usage (with random salt): // Usage (with random salt):
// core.NewCSRF("", "super secret", "csrf_session", store, func (c *gin.Context) { // core.NewCSRF("", "super secret", "csrf_session", store, func (c *gin.Context, reason core.CSRFErrorReason) {
// c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid CSRF token"}) // c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid CSRF token"})
// }, core.DefaultCSRFTokenGetter) // }, core.DefaultCSRFTokenGetter)
// Note for csrfTokenGetter: if you want to read token from request body (for example, from form field) - don't forget to restore Body data! // Note for csrfTokenGetter: if you want to read token from request body (for example, from form field) - don't forget to restore Body data!
@ -69,7 +93,8 @@ type CSRF struct {
// } // }
// will close body - and all next middlewares won't be able to read body at all! // will close body - and all next middlewares won't be able to read body at all!
// Use DefaultCSRFTokenGetter as example to implement your own token getter. // Use DefaultCSRFTokenGetter as example to implement your own token getter.
func NewCSRF(salt, secret, sessionName string, store sessions.Store, abortFunc gin.HandlerFunc, csrfTokenGetter CSRFTokenGetter) *CSRF { // CSRFErrorReason will be passed to abortFunc and can be used for better error messages.
func NewCSRF(salt, secret, sessionName string, store sessions.Store, abortFunc CSRFAbortFunc, csrfTokenGetter CSRFTokenGetter) *CSRF {
if store == nil { if store == nil {
panic("store must not be nil") panic("store must not be nil")
} }
@ -115,8 +140,12 @@ func (x *CSRF) strInSlice(slice []string, v string) bool {
// generateCSRFToken generates new CSRF token // generateCSRFToken generates new CSRF token
func (x *CSRF) generateCSRFToken() string { func (x *CSRF) generateCSRFToken() string {
// nolint:gosec
h := sha1.New() h := sha1.New()
io.WriteString(h, x.salt+"#"+x.secret) // Fallback to less secure method - token must be always filled even if we cannot properly generate it
if _, err := io.WriteString(h, x.salt+"#"+x.secret); err != nil {
return base64.URLEncoding.EncodeToString([]byte(time.Now().String()))
}
hash := base64.URLEncoding.EncodeToString(h.Sum(nil)) hash := base64.URLEncoding.EncodeToString(h.Sum(nil))
return hash return hash
@ -155,12 +184,10 @@ func (x *CSRF) CSRFFromContext(c *gin.Context) string {
if i, ok := c.Get("csrf_token"); ok { if i, ok := c.Get("csrf_token"); ok {
if token, ok := i.(string); ok { if token, ok := i.(string); ok {
return token return token
} else {
return x.generateCSRFToken()
} }
} else {
return x.generateCSRFToken()
} }
return x.generateCSRFToken()
} }
// GenerateCSRFMiddleware returns gin.HandlerFunc which will generate CSRF token // GenerateCSRFMiddleware returns gin.HandlerFunc which will generate CSRF token
@ -175,14 +202,14 @@ func (x *CSRF) GenerateCSRFMiddleware() gin.HandlerFunc {
if i, ok := session.Values["csrf_token"]; ok { if i, ok := session.Values["csrf_token"]; ok {
if i, ok := i.(string); !ok || i == "" { if i, ok := i.(string); !ok || i == "" {
if x.fillToken(session, c) != nil { if x.fillToken(session, c) != nil {
x.abortFunc(c) x.abortFunc(c, CSRFErrorCannotStoreTokenInSession)
c.Abort() c.Abort()
return return
} }
} }
} else { } else {
if x.fillToken(session, c) != nil { if x.fillToken(session, c) != nil {
x.abortFunc(c) x.abortFunc(c, CSRFErrorCannotStoreTokenInSession)
c.Abort() c.Abort()
return return
} }
@ -211,23 +238,47 @@ func (x *CSRF) VerifyCSRFMiddleware(ignoredMethods []string) gin.HandlerFunc {
session, _ := x.store.Get(c.Request, x.sessionName) session, _ := x.store.Get(c.Request, x.sessionName)
if i, ok := session.Values["csrf_token"]; ok { if i, ok := session.Values["csrf_token"]; ok {
if i, ok := i.(string); !ok || i == "" { var v string
x.abortFunc(c) if v, ok = i.(string); !ok || v == "" {
if !ok {
x.abortFunc(c, CSRFErrorIncorrectTokenType)
} else if v == "" {
x.abortFunc(c, CSRFErrorEmptyToken)
}
c.Abort() c.Abort()
return return
} else {
token = i
} }
token = v
} else { } else {
x.abortFunc(c) x.abortFunc(c, CSRFErrorNoTokenInSession)
c.Abort() c.Abort()
return return
} }
if x.csrfTokenGetter(c) != token { if x.csrfTokenGetter(c) != token {
x.abortFunc(c) x.abortFunc(c, CSRFErrorTokenMismatch)
c.Abort() c.Abort()
return return
} }
} }
} }
// GetCSRFErrorMessage returns generic error message for CSRFErrorReason in English (useful for logs)
func GetCSRFErrorMessage(r CSRFErrorReason) string {
switch r {
case CSRFErrorNoTokenInSession:
return "token is not present in session"
case CSRFErrorCannotStoreTokenInSession:
return "cannot store token in session"
case CSRFErrorIncorrectTokenType:
return "incorrect token type"
case CSRFErrorEmptyToken:
return "empty token present in session"
case CSRFErrorTokenMismatch:
return "token mismatch"
default:
return "unknown error"
}
}

View File

@ -101,7 +101,7 @@ func TestCSRF_NewCSRF_NilStore(t *testing.T) {
assert.NotNil(t, recover()) assert.NotNil(t, recover())
}() }()
NewCSRF("salt", "secret", "csrf", nil, func(context *gin.Context) {}, DefaultCSRFTokenGetter) NewCSRF("salt", "secret", "csrf", nil, func(c *gin.Context, r CSRFErrorReason) {}, DefaultCSRFTokenGetter)
} }
func TestCSRF_NewCSRF_EmptySecret(t *testing.T) { func TestCSRF_NewCSRF_EmptySecret(t *testing.T) {
@ -110,23 +110,38 @@ func TestCSRF_NewCSRF_EmptySecret(t *testing.T) {
}() }()
store := sessions.NewCookieStore([]byte("keys")) store := sessions.NewCookieStore([]byte("keys"))
NewCSRF("salt", "", "csrf", store, func(context *gin.Context) {}, DefaultCSRFTokenGetter) NewCSRF("salt", "", "csrf", store, func(c *gin.Context, r CSRFErrorReason) {}, DefaultCSRFTokenGetter)
} }
func TestCSRF_NewCSRF_SaltAndSessionNotEmpty(t *testing.T) { func TestCSRF_NewCSRF_SaltAndSessionNotEmpty(t *testing.T) {
store := sessions.NewCookieStore([]byte("keys")) store := sessions.NewCookieStore([]byte("keys"))
csrf := NewCSRF("salt", "secret", "", store, func(context *gin.Context) {}, DefaultCSRFTokenGetter) csrf := NewCSRF("salt", "secret", "", store, func(c *gin.Context, r CSRFErrorReason) {}, DefaultCSRFTokenGetter)
assert.NotEmpty(t, csrf.salt) assert.NotEmpty(t, csrf.salt)
assert.NotEmpty(t, csrf.sessionName) assert.NotEmpty(t, csrf.sessionName)
} }
func TestCSRF_GetCSRFErrorMessage(t *testing.T) {
items := map[CSRFErrorReason]string{
CSRFErrorNoTokenInSession: "token is not present in session",
CSRFErrorCannotStoreTokenInSession: "cannot store token in session",
CSRFErrorIncorrectTokenType: "incorrect token type",
CSRFErrorEmptyToken: "empty token present in session",
CSRFErrorTokenMismatch: "token mismatch",
99: "unknown error",
}
for reason, message := range items {
assert.Equal(t, message, GetCSRFErrorMessage(reason))
}
}
func TestCSRF_Suite(t *testing.T) { func TestCSRF_Suite(t *testing.T) {
suite.Run(t, new(CSRFTest)) suite.Run(t, new(CSRFTest))
} }
func (x *CSRFTest) SetupSuite() { func (x *CSRFTest) SetupSuite() {
store := sessions.NewCookieStore([]byte("keys")) store := sessions.NewCookieStore([]byte("keys"))
x.csrf = NewCSRF("salt", "secret", "", store, func(context *gin.Context) { x.csrf = NewCSRF("salt", "secret", "", store, func(context *gin.Context, r CSRFErrorReason) {
context.AbortWithStatus(900) context.AbortWithStatus(900)
}, DefaultCSRFTokenGetter) }, DefaultCSRFTokenGetter)
} }

97
core/doc.go Normal file
View File

@ -0,0 +1,97 @@
// Copyright (c) 2019 RetailDriver LLC
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.
/*
Package core provides different functions like error-reporting, logging, localization, etc. in order to make it easier to create transports.
Usage:
package main
import (
"os"
"fmt"
"html/template"
"github.com/gin-gonic/gin"
"github.com/gobuffalo/packr/v2"
"github.com/retailcrm/mg-transport-core/core"
)
func main() {
app := core.New()
app.Config = core.NewConfig("config.yml")
app.DefaultError = "unknown_error"
app.TranslationsPath = "./translations"
app.ConfigureRouter(func(engine *gin.Engine) {
engine.Static("/static", "./static")
engine.HTMLRender = app.CreateRenderer(
func(renderer *core.Renderer) {
// insert templates here. Example:
r.Push("home", "templates/layout.html", "templates/home.html")
},
template.FuncMap{},
)
})
if err := app.Prepare().Run(); err != nil {
fmt.Printf("Fatal error: %s", err.Error())
os.Exit(1)
}
}
Resource embedding
packr can be used to provide resource embedding, see:
https://github.com/gobuffalo/packr/tree/master/v2
In order to use packr you must follow instruction, and provide boxes with templates, translations and assets to library.
You can find instruction here:
https://github.com/gobuffalo/packr/tree/master/v2#library-installation
Example of usage:
package main
import (
"os"
"fmt"
"html/template"
"github.com/gin-gonic/gin"
"github.com/gobuffalo/packr/v2"
"github.com/retailcrm/mg-transport-core/core"
)
func main() {
static := packr.New("assets", "./static")
templates := packr.New("templates", "./templates")
translations := packr.New("translations", "./translate")
app := core.New()
app.Config = core.NewConfig("config.yml")
app.DefaultError = "unknown_error"
app.TranslationsBox = translations
app.ConfigureRouter(func(engine *gin.Engine) {
engine.StaticFS("/static", static)
engine.HTMLRender = app.CreateRendererFS(
templates,
func(renderer *core.Renderer) {
// insert templates here. Example:
r.Push("home", "layout.html", "home.html")
},
template.FuncMap{},
)
})
if err := app.Prepare().Run(); err != nil {
fmt.Printf("Fatal error: %s", err.Error())
os.Exit(1)
}
}
Migration generator
This library contains helper tool for transports. You can install it via go:
$ go get -u github.com/retailcrm/mg-transport-core/cmd/transport-core-tool
Currently, it only can generate new migrations for your transport.
*/
package core

View File

@ -92,7 +92,7 @@ func (e *Engine) Prepare() *Engine {
return e return e
} }
// templateFuncMap combines func map for templates // TemplateFuncMap combines func map for templates
func (e *Engine) TemplateFuncMap(functions template.FuncMap) template.FuncMap { func (e *Engine) TemplateFuncMap(functions template.FuncMap) template.FuncMap {
funcMap := e.LocalizationFuncMap() funcMap := e.LocalizationFuncMap()
@ -100,6 +100,10 @@ func (e *Engine) TemplateFuncMap(functions template.FuncMap) template.FuncMap {
funcMap[name] = fn funcMap[name] = fn
} }
funcMap["version"] = func() string {
return e.Config.GetVersion()
}
return funcMap return funcMap
} }
@ -133,7 +137,12 @@ func (e *Engine) Router() *gin.Engine {
// BuildHTTPClient builds HTTP client with provided configuration // BuildHTTPClient builds HTTP client with provided configuration
func (e *Engine) BuildHTTPClient(replaceDefault ...bool) *Engine { func (e *Engine) BuildHTTPClient(replaceDefault ...bool) *Engine {
if e.Config.GetHTTPClientConfig() != nil { if e.Config.GetHTTPClientConfig() != nil {
if client, err := NewHTTPClientBuilder().FromEngine(e).Build(replaceDefault...); err != nil { client, err := NewHTTPClientBuilder().
WithLogger(e.Logger).
SetLogging(e.Config.IsDebug()).
FromEngine(e).Build(replaceDefault...)
if err != nil {
panic(err) panic(err)
} else { } else {
e.httpClient = client e.httpClient = client
@ -156,9 +165,9 @@ func (e *Engine) SetHTTPClient(client *http.Client) *Engine {
func (e *Engine) HTTPClient() *http.Client { func (e *Engine) HTTPClient() *http.Client {
if e.httpClient == nil { if e.httpClient == nil {
return http.DefaultClient return http.DefaultClient
} else {
return e.httpClient
} }
return e.httpClient
} }
// WithCookieSessions generates new CookieStore with optional key length. // WithCookieSessions generates new CookieStore with optional key length.
@ -174,7 +183,7 @@ func (e *Engine) WithCookieSessions(keyLength ...int) *Engine {
return e return e
} }
// WithCookieSessions generates new FilesystemStore with optional key length. // WithFilesystemSessions generates new FilesystemStore with optional key length.
// Default key length is 32 bytes. // Default key length is 32 bytes.
func (e *Engine) WithFilesystemSessions(path string, keyLength ...int) *Engine { func (e *Engine) WithFilesystemSessions(path string, keyLength ...int) *Engine {
length := 32 length := 32
@ -190,7 +199,7 @@ func (e *Engine) WithFilesystemSessions(path string, keyLength ...int) *Engine {
// InitCSRF initializes CSRF middleware. engine.Sessions must be already initialized, // InitCSRF initializes CSRF middleware. engine.Sessions must be already initialized,
// use engine.WithCookieStore or engine.WithFilesystemStore for that. // use engine.WithCookieStore or engine.WithFilesystemStore for that.
// Syntax is similar to core.NewCSRF, but you shouldn't pass sessionName, store and salt. // Syntax is similar to core.NewCSRF, but you shouldn't pass sessionName, store and salt.
func (e *Engine) InitCSRF(secret string, abortFunc gin.HandlerFunc, getter CSRFTokenGetter) *Engine { func (e *Engine) InitCSRF(secret string, abortFunc CSRFAbortFunc, getter CSRFTokenGetter) *Engine {
if e.Sessions == nil { if e.Sessions == nil {
panic("engine.Sessions must be initialized first") panic("engine.Sessions must be initialized first")
} }

View File

@ -154,7 +154,7 @@ func (e *EngineTest) Test_BuildHTTPClient() {
e.engine.Config = &Config{ e.engine.Config = &Config{
HTTPClientConfig: &HTTPClientConfig{ HTTPClientConfig: &HTTPClientConfig{
Timeout: 30, Timeout: 30,
SSLVerification: true, SSLVerification: boolPtr(true),
}, },
} }
e.engine.BuildHTTPClient() e.engine.BuildHTTPClient()
@ -204,7 +204,7 @@ func (e *EngineTest) Test_InitCSRF_Fail() {
e.engine.csrf = nil e.engine.csrf = nil
e.engine.Sessions = nil e.engine.Sessions = nil
e.engine.InitCSRF("test", func(context *gin.Context) {}, DefaultCSRFTokenGetter) e.engine.InitCSRF("test", func(context *gin.Context, r CSRFErrorReason) {}, DefaultCSRFTokenGetter)
assert.Nil(e.T(), e.engine.csrf) assert.Nil(e.T(), e.engine.csrf)
} }
@ -215,7 +215,7 @@ func (e *EngineTest) Test_InitCSRF() {
e.engine.csrf = nil e.engine.csrf = nil
e.engine.WithCookieSessions(4) e.engine.WithCookieSessions(4)
e.engine.InitCSRF("test", func(context *gin.Context) {}, DefaultCSRFTokenGetter) e.engine.InitCSRF("test", func(context *gin.Context, r CSRFErrorReason) {}, DefaultCSRFTokenGetter)
assert.NotNil(e.T(), e.engine.csrf) assert.NotNil(e.T(), e.engine.csrf)
} }
@ -235,7 +235,7 @@ func (e *EngineTest) Test_VerifyCSRFMiddleware() {
e.engine.csrf = nil e.engine.csrf = nil
e.engine.WithCookieSessions(4) e.engine.WithCookieSessions(4)
e.engine.InitCSRF("test", func(context *gin.Context) {}, DefaultCSRFTokenGetter) e.engine.InitCSRF("test", func(context *gin.Context, r CSRFErrorReason) {}, DefaultCSRFTokenGetter)
e.engine.VerifyCSRFMiddleware(DefaultIgnoredMethods) e.engine.VerifyCSRFMiddleware(DefaultIgnoredMethods)
} }
@ -255,7 +255,7 @@ func (e *EngineTest) Test_GenerateCSRFMiddleware() {
e.engine.csrf = nil e.engine.csrf = nil
e.engine.WithCookieSessions(4) e.engine.WithCookieSessions(4)
e.engine.InitCSRF("test", func(context *gin.Context) {}, DefaultCSRFTokenGetter) e.engine.InitCSRF("test", func(context *gin.Context, r CSRFErrorReason) {}, DefaultCSRFTokenGetter)
e.engine.GenerateCSRFMiddleware() e.engine.GenerateCSRFMiddleware()
} }
@ -284,7 +284,7 @@ func (e *EngineTest) Test_GetCSRFToken() {
e.engine.csrf = nil e.engine.csrf = nil
e.engine.WithCookieSessions(4) e.engine.WithCookieSessions(4)
e.engine.InitCSRF("test", func(context *gin.Context) {}, DefaultCSRFTokenGetter) e.engine.InitCSRF("test", func(context *gin.Context, r CSRFErrorReason) {}, DefaultCSRFTokenGetter)
assert.NotEmpty(e.T(), e.engine.GetCSRFToken(c)) assert.NotEmpty(e.T(), e.engine.GetCSRFToken(c))
assert.Equal(e.T(), "token", e.engine.GetCSRFToken(c)) assert.Equal(e.T(), "token", e.engine.GetCSRFToken(c))
} }
@ -300,3 +300,8 @@ func (e *EngineTest) Test_Run_Fail() {
func TestEngine_Suite(t *testing.T) { func TestEngine_Suite(t *testing.T) {
suite.Run(t, new(EngineTest)) suite.Run(t, new(EngineTest))
} }
func boolPtr(val bool) *bool {
b := val
return &b
}

View File

@ -8,13 +8,15 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/op/go-logging"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
var ( // DefaultClient stores original http.DefaultClient
DefaultClient = http.DefaultClient var DefaultClient = http.DefaultClient
DefaultTransport = http.DefaultTransport
) // DefaultTransport stores original http.DefaultTransport
var DefaultTransport = http.DefaultTransport
// HTTPClientBuilder builds http client with mocks (if necessary) and timeout. // HTTPClientBuilder builds http client with mocks (if necessary) and timeout.
// Example: // Example:
@ -44,7 +46,7 @@ type HTTPClientBuilder struct {
httpClient *http.Client httpClient *http.Client
httpTransport *http.Transport httpTransport *http.Transport
dialer *net.Dialer dialer *net.Dialer
engine *Engine logger *logging.Logger
built bool built bool
logging bool logging bool
timeout time.Duration timeout time.Duration
@ -67,6 +69,15 @@ func NewHTTPClientBuilder() *HTTPClientBuilder {
} }
} }
// WithLogger sets provided logger into HTTPClientBuilder
func (b *HTTPClientBuilder) WithLogger(logger *logging.Logger) *HTTPClientBuilder {
if logger != nil {
b.logger = logger
}
return b
}
// SetTimeout sets timeout for http client // SetTimeout sets timeout for http client
func (b *HTTPClientBuilder) SetTimeout(timeout time.Duration) *HTTPClientBuilder { func (b *HTTPClientBuilder) SetTimeout(timeout time.Duration) *HTTPClientBuilder {
timeout = timeout * time.Second timeout = timeout * time.Second
@ -104,9 +115,9 @@ func (b *HTTPClientBuilder) SetSSLVerification(enabled bool) *HTTPClientBuilder
return b return b
} }
// EnableLogging enables logging in mocks // SetLogging enables or disables logging in mocks
func (b *HTTPClientBuilder) EnableLogging() *HTTPClientBuilder { func (b *HTTPClientBuilder) SetLogging(flag bool) *HTTPClientBuilder {
b.logging = true b.logging = flag
return b return b
} }
@ -125,15 +136,13 @@ func (b *HTTPClientBuilder) FromConfig(config *HTTPClientConfig) *HTTPClientBuil
b.SetTimeout(config.Timeout) b.SetTimeout(config.Timeout)
} }
b.SetSSLVerification(config.SSLVerification) b.SetSSLVerification(config.IsSSLVerificationEnabled())
return b return b
} }
// FromEngine fulfills mock configuration from ConfigInterface inside Engine // FromEngine fulfills mock configuration from ConfigInterface inside Engine
func (b *HTTPClientBuilder) FromEngine(engine *Engine) *HTTPClientBuilder { func (b *HTTPClientBuilder) FromEngine(engine *Engine) *HTTPClientBuilder {
b.engine = engine
b.logging = engine.Config.IsDebug()
return b.FromConfig(engine.Config.GetHTTPClientConfig()) return b.FromConfig(engine.Config.GetHTTPClientConfig())
} }
@ -156,10 +165,11 @@ func (b *HTTPClientBuilder) parseAddress() error {
if host, port, err := net.SplitHostPort(b.mockAddress); err == nil { if host, port, err := net.SplitHostPort(b.mockAddress); err == nil {
b.mockHost = host b.mockHost = host
b.mockPort = port b.mockPort = port
return nil
} else { } else {
return errors.Errorf("cannot split host and port: %s", err.Error()) return errors.Errorf("cannot split host and port: %s", err.Error())
} }
return nil
} }
// buildMocks builds mocks for http client // buildMocks builds mocks for http client
@ -177,9 +187,15 @@ func (b *HTTPClientBuilder) buildMocks() error {
} }
b.httpTransport.DialContext = func(ctx context.Context, network, addr string) (conn net.Conn, e error) { b.httpTransport.DialContext = func(ctx context.Context, network, addr string) (conn net.Conn, e error) {
if host, port, err := net.SplitHostPort(addr); err != nil { var (
host string
port string
err error
)
if host, port, err = net.SplitHostPort(addr); err != nil {
return b.dialer.DialContext(ctx, network, addr) return b.dialer.DialContext(ctx, network, addr)
} else { }
for _, mock := range b.mockedDomains { for _, mock := range b.mockedDomains {
if mock == host { if mock == host {
oldAddr := addr oldAddr := addr
@ -193,7 +209,6 @@ func (b *HTTPClientBuilder) buildMocks() error {
b.logf("Mocking \"%s\" with \"%s\"\n", oldAddr, addr) b.logf("Mocking \"%s\" with \"%s\"\n", oldAddr, addr)
} }
} }
}
return b.dialer.DialContext(ctx, network, addr) return b.dialer.DialContext(ctx, network, addr)
} }
@ -205,8 +220,8 @@ func (b *HTTPClientBuilder) buildMocks() error {
// logf prints logs via Engine or via fmt.Printf // logf prints logs via Engine or via fmt.Printf
func (b *HTTPClientBuilder) logf(format string, args ...interface{}) { func (b *HTTPClientBuilder) logf(format string, args ...interface{}) {
if b.logging { if b.logging {
if b.engine != nil && b.engine.Logger != nil { if b.logger != nil {
b.engine.Logger.Infof(format, args...) b.logger.Infof(format, args...)
} else { } else {
fmt.Printf(format, args...) fmt.Printf(format, args...)
} }

View File

@ -1,11 +1,21 @@
package core package core
import ( import (
"context"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http" "net/http"
"os"
"strings"
"testing" "testing"
"time" "time"
"github.com/op/go-logging"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
) )
@ -25,6 +35,14 @@ func (t *HTTPClientBuilderTest) Test_SetTimeout() {
assert.Equal(t.T(), 90*time.Second, t.builder.httpClient.Timeout) assert.Equal(t.T(), 90*time.Second, t.builder.httpClient.Timeout)
} }
func (t *HTTPClientBuilderTest) Test_SetLogging() {
t.builder.SetLogging(true)
assert.True(t.T(), t.builder.logging)
t.builder.SetLogging(false)
assert.False(t.T(), t.builder.logging)
}
func (t *HTTPClientBuilderTest) Test_SetMockAddress() { func (t *HTTPClientBuilderTest) Test_SetMockAddress() {
addr := "mock.local:3004" addr := "mock.local:3004"
t.builder.SetMockAddress(addr) t.builder.SetMockAddress(addr)
@ -56,16 +74,23 @@ func (t *HTTPClientBuilderTest) Test_SetSSLVerification() {
assert.True(t.T(), t.builder.httpTransport.TLSClientConfig.InsecureSkipVerify) assert.True(t.T(), t.builder.httpTransport.TLSClientConfig.InsecureSkipVerify)
} }
func (t *HTTPClientBuilderTest) Test_FromConfigNil() {
defer func() {
assert.Nil(t.T(), recover())
}()
t.builder.FromConfig(nil)
}
func (t *HTTPClientBuilderTest) Test_FromConfig() { func (t *HTTPClientBuilderTest) Test_FromConfig() {
config := &HTTPClientConfig{ config := &HTTPClientConfig{
SSLVerification: true, SSLVerification: boolPtr(true),
MockAddress: "anothermock.local:3004", MockAddress: "anothermock.local:3004",
MockedDomains: []string{"example.gov"}, MockedDomains: []string{"example.gov"},
Timeout: 60, Timeout: 60,
} }
t.builder.FromConfig(config) t.builder.FromConfig(config)
assert.Equal(t.T(), !config.SSLVerification, t.builder.httpTransport.TLSClientConfig.InsecureSkipVerify) assert.Equal(t.T(), !config.IsSSLVerificationEnabled(), t.builder.httpTransport.TLSClientConfig.InsecureSkipVerify)
assert.Equal(t.T(), config.MockAddress, t.builder.mockAddress) assert.Equal(t.T(), config.MockAddress, t.builder.mockAddress)
assert.Equal(t.T(), config.MockedDomains[0], t.builder.mockedDomains[0]) assert.Equal(t.T(), config.MockedDomains[0], t.builder.mockedDomains[0])
assert.Equal(t.T(), config.Timeout*time.Second, t.builder.timeout) assert.Equal(t.T(), config.Timeout*time.Second, t.builder.timeout)
@ -76,7 +101,7 @@ func (t *HTTPClientBuilderTest) Test_FromEngine() {
engine := &Engine{ engine := &Engine{
Config: Config{ Config: Config{
HTTPClientConfig: &HTTPClientConfig{ HTTPClientConfig: &HTTPClientConfig{
SSLVerification: true, SSLVerification: boolPtr(true),
MockAddress: "anothermock.local:3004", MockAddress: "anothermock.local:3004",
MockedDomains: []string{"example.gov"}, MockedDomains: []string{"example.gov"},
}, },
@ -85,7 +110,7 @@ func (t *HTTPClientBuilderTest) Test_FromEngine() {
} }
t.builder.FromEngine(engine) t.builder.FromEngine(engine)
assert.NotNil(t.T(), engine, t.builder.engine) assert.Equal(t.T(), engine.Config.GetHTTPClientConfig().MockAddress, t.builder.mockAddress)
} }
func (t *HTTPClientBuilderTest) Test_buildDialer() { func (t *HTTPClientBuilderTest) Test_buildDialer() {
@ -102,6 +127,18 @@ func (t *HTTPClientBuilderTest) Test_buildMocks() {
assert.NoError(t.T(), t.builder.buildMocks()) assert.NoError(t.T(), t.builder.buildMocks())
} }
func (t *HTTPClientBuilderTest) Test_WithLogger() {
logger := NewLogger("telegram", logging.ERROR, DefaultLogFormatter())
builder := NewHTTPClientBuilder()
require.Nil(t.T(), builder.logger)
builder.WithLogger(nil)
assert.Nil(t.T(), builder.logger)
builder.WithLogger(logger)
assert.NotNil(t.T(), builder.logger)
}
func (t *HTTPClientBuilderTest) Test_logf() { func (t *HTTPClientBuilderTest) Test_logf() {
defer func() { defer func() {
assert.Nil(t.T(), recover()) assert.Nil(t.T(), recover())
@ -130,6 +167,151 @@ func (t *HTTPClientBuilderTest) Test_RestoreDefault() {
assert.Equal(t.T(), http.DefaultTransport, DefaultTransport) assert.Equal(t.T(), http.DefaultTransport, DefaultTransport)
} }
// Test_ClientMocksWorking is supposed to test mocking functionality of generated client.
// Using real HTTP requests and server doesn't look good obviously, but mocking something to check how
// mocks are working doesn't look that good too.
// In this case we are trying to test this in just-like-real environment, without mocks or etc.
// Fake "real" environment is easiest way to do this.
// You know how to make this test better? Let us know.
func (t *HTTPClientBuilderTest) Test_ClientMocksWorking() {
mockProto := "https://"
mockServerAddr := getOutboundIP().String() + ":27717"
mockDomainAddr := "example.com"
certFileData := `-----BEGIN CERTIFICATE-----
MIIC+TCCAeGgAwIBAgIJAPb0Qm9aV+93MA0GCSqGSIb3DQEBBQUAMBMxETAPBgNV
BAMMCGFwaV9tb2NrMB4XDTE5MTAxNzE0NDMzMloXDTI5MTAxNDE0NDMzMlowEzER
MA8GA1UEAwwIYXBpX21vY2swggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQCtxUg6aUBcZLjEYEgzzE+B8wAsyK6dT9fbbDfO/5n9PhHjxQLnjTYrs9xAAl7R
Caj8nUg0RzfX38jD1TGwkFrC1u1pjOf74OVMXsw2xa7gmZlnJeeL+QozXQX1rDPk
wO5QqomKAIwM3ab+i6k1tLBfDIOHGLhTEFQZ9cmKVuNdTlkeqBh+bKduRIr7DYhQ
Dsci/PacGJlt0W+r2YuRmm1KGearbS4HabPOkC0c6KbVD+bUyF+F7DxtJ8vg7O4W
SWAXlwoEHonIyJG8H7TQxL2g5w/4UQhp6awAzFNLhqtf/6pw6gPfI9joS+Z/Pxvz
Bry41s4LV7KP8v0GqRK3KH2rAgMBAAGjUDBOMB0GA1UdDgQWBBTEiHl+R8N0kLwH
1RTsKYe8joAwxzAfBgNVHSMEGDAWgBTEiHl+R8N0kLwH1RTsKYe8joAwxzAMBgNV
HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAo/XBUFlrl5yuQ+5eFQjMmbn4T
9qVg4NVRy5qnTKzcOR21X3fB394TvfBiGstW0YQCOCtNakv94UAbZozYktQaYOtP
x5porosgI2RgOTTwmiYOcYQTS2650jYydHhK16Gu2b3UKernO16mAWXNDWfvS2bk
1ufbRWpuUXs0SIR6e/mgSwrBMBvq6fan4EVdEkx4Catjna15DgmBGRL215t5K4aq
nAI2GL2ACEdOCyRvgq16AycJJYU7nYQ+t9aveefx0uhbYYIVeYub9NxmCfD3MojI
saG/63vo0ng851n90DVoMRWx9n1CjEvss/vvz+jXIl9njaCtizN3WUf1NwUB
-----END CERTIFICATE-----`
keyFileData := `-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEArcVIOmlAXGS4xGBIM8xPgfMALMiunU/X22w3zv+Z/T4R48UC
5402K7PcQAJe0Qmo/J1INEc319/Iw9UxsJBawtbtaYzn++DlTF7MNsWu4JmZZyXn
i/kKM10F9awz5MDuUKqJigCMDN2m/oupNbSwXwyDhxi4UxBUGfXJilbjXU5ZHqgY
fmynbkSK+w2IUA7HIvz2nBiZbdFvq9mLkZptShnmq20uB2mzzpAtHOim1Q/m1Mhf
hew8bSfL4OzuFklgF5cKBB6JyMiRvB+00MS9oOcP+FEIaemsAMxTS4arX/+qcOoD
3yPY6Evmfz8b8wa8uNbOC1eyj/L9BqkStyh9qwIDAQABAoIBAG/jNUyW9KAJIocf
T546kX8kzhoH5ZbZEC3ykkSwg6Bx1OcJtYMOg7DEEK8OV6rVQ3/Ubedra+ON7iFa
JrJ/YFFQPnHWDpE6D6qK54bk5mMrw4CNAXg5FH5aCTpUdN139HrwM7st+v9VwO7p
UjyIaX/p+M6F9jlVrDFC91Ah/ifW+DzWS27IsQ/396HTvTUNgD/Lj9pKtC6eW3AQ
aTpLk/cVrkxxT1qc4rx8NRPe9/670CQDQYMvaNJtC54kRUzH87mn06IEy5fuLeI4
WwYgEIkh1YjQsJhg3Z788JGKR5iKuzjXpw7aD6QESISoWPW1Dh/5PJO1zkKxZril
xUepcgECgYEA5MLP4oCiuloShhaMc+UMYgH8YuglXegGbF5/iG9d7kgaiz4KiXuD
ObwaHegLDFNSxtVSiH6AsXErzd7HHojzTm6B3O5qtBl+QgpW9hiTyJnCxkdzxK7y
cVan8Jp3g/ojcYBY+QgZlR81QHQIwSUrAbMC0fjYlrKb5zJfExgFv4ECgYEAwnY7
MUoLsHs2eXVOekvb42ReCq7HKVz8TETlSEPN4B5XzOKCnXxCUUvF5iWjWj0XAMSU
yF6LJmmfFmOaVHpXDHhF9MmNKxYiIISS+ZM6B2DXP+hS/DXXuy5hGmYO6p4JC08d
qulIIR7JSJmHyI+5Ref40WTObJvJQDXi2p5a0ysCgYBjdONG2aBmHrUBARqtZH7e
uXhOVBmy2ya3xNnzql+PMl//+8g+/6kM1+AO8oyjHjLV6XcJit5OxyJBTkMJ3obR
qa/iKvHPPWosMiyesA7IXzlUVUpaz6juZ7t6Gt4tTfpM5X1JQCFHORtA23HW717k
TTzDp0obMqofeUHmnkIZgQKBgFBaGktblUjvIKs/VZYjElD7gABaB+GHkpjRPwyF
N+SLpSv7zIzWc3C0Jqnak40OARtIH1JL/qN4sUvHDFYr1xxH9mAXiEVtd9yH61NF
Co1R7p9xmBivBt1JZMZLtY4sjwAlSNT+X9ePqQxepESzXpMMLzwWs1UdaiMmIP7E
wDLRAoGAM/Cz7B+J7KcKI8VqXAsX5nMklIKvACScKU6oIPwNYXyxUfm7jsNaBqXG
weywTxDl/OD5ybNkZIRKsIXciFYG1VCGO2HNGN9qJcV+nJ63kyrIBauwUkuEhiN5
uf/TQPpjrGW5nxOf94qn6FzV2WSype9BcM5MD7z7rk202Fs7Zqc=
-----END RSA PRIVATE KEY-----`
certFile, err := ioutil.TempFile("/tmp", "cert_")
require.NoError(t.T(), err, "cannot create temp cert file")
keyFile, err := ioutil.TempFile("/tmp", "key_")
require.NoError(t.T(), err, "cannot create temp key file")
_, err = certFile.WriteString(certFileData)
require.NoError(t.T(), err, "cannot write temp cert file")
_, err = keyFile.WriteString(keyFileData)
require.NoError(t.T(), err, "cannot write temp key file")
require.NoError(t.T(),
ErrorCollector(certFile.Sync(), certFile.Close()), "cannot sync and close temp cert file")
require.NoError(t.T(),
ErrorCollector(keyFile.Sync(), keyFile.Close()), "cannot sync and close temp key file")
mux := &http.ServeMux{}
srv := &http.Server{Addr: mockServerAddr, Handler: mux}
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusCreated)
_, _ = io.WriteString(w, "ok")
})
testSkipChan := make(chan error, 1)
go func(skip chan error) {
if err := srv.ListenAndServeTLS(certFile.Name(), keyFile.Name()); err != nil && err != http.ErrServerClosed {
skip <- fmt.Errorf("skipping test because server won't start: %s", err.Error())
}
}(testSkipChan)
select {
case errStartup := <-testSkipChan:
t.T().Skip(errStartup)
return
case <-time.After(time.Second):
t.T().Log("test server started")
}
defer func() {
if err := srv.Shutdown(context.TODO()); err != nil {
t.T().Log("warning > cannot shutdown server gracefully: ", err)
} else {
t.T().Log("test server stopped")
}
if err := os.Remove(certFile.Name()); err != nil {
t.T().Log("warning > cannot remove temp cert file properly: ", err)
}
if err := os.Remove(keyFile.Name()); err != nil {
t.T().Log("warning > cannot remove temp key file properly: ", err)
}
}()
client, err := NewHTTPClientBuilder().
SetLogging(true).
SetMockAddress(mockServerAddr).
SetMockedDomains([]string{mockDomainAddr}).
SetTimeout(time.Second).
SetSSLVerification(false).
Build()
require.NoError(t.T(), err, "cannot build client")
resp, err := client.Get(mockProto + mockDomainAddr)
if err != nil && strings.Contains(err.Error(), "connection refused") {
t.T().Skip("connection refused - skipping test: ", err)
}
require.NoError(t.T(), err, "error while making request")
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
require.NoError(t.T(), err, "error while reading body")
assert.Equal(t.T(), http.StatusCreated, resp.StatusCode, "invalid status code")
assert.Equal(t.T(), "ok", string(data), "invalid body contents")
}
// taken from https://stackoverflow.com/questions/23558425/how-do-i-get-the-local-ip-address-in-go
func getOutboundIP() net.IP {
conn, err := net.Dial("udp", "8.8.8.8:80")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
localAddr := conn.LocalAddr().(*net.UDPAddr)
return localAddr.IP
}
func Test_HTTPClientBuilder(t *testing.T) { func Test_HTTPClientBuilder(t *testing.T) {
suite.Run(t, new(HTTPClientBuilderTest)) suite.Run(t, new(HTTPClientBuilderTest))
} }

View File

@ -145,9 +145,9 @@ func (l *Localizer) loadFromFS() error {
if err != nil { if err != nil {
return err return err
} else {
return nil
} }
return nil
} }
// SetLocale will change language for current localizer // SetLocale will change language for current localizer

View File

@ -8,7 +8,7 @@ import (
// NewLogger will create new logger with specified formatter. // NewLogger will create new logger with specified formatter.
// Usage: // Usage:
// logger := NewLogger(config, DefaultLogFormatter()) // logger := NewLogger("telegram", logging.ERROR, DefaultLogFormatter())
func NewLogger(transportCode string, logLevel logging.Level, logFormat logging.Formatter) *logging.Logger { func NewLogger(transportCode string, logLevel logging.Level, logFormat logging.Formatter) *logging.Logger {
logger := logging.MustGetLogger(transportCode) logger := logging.MustGetLogger(transportCode)
logBackend := logging.NewLogBackend(os.Stdout, "", 0) logBackend := logging.NewLogBackend(os.Stdout, "", 0)

View File

@ -54,7 +54,7 @@ func (m *Migrate) Add(migration *gormigrate.Migration) {
m.migrations[migration.ID] = migration m.migrations[migration.ID] = migration
} }
// SetORM to migrate // SetDB to migrate
func (m *Migrate) SetDB(db *gorm.DB) *Migrate { func (m *Migrate) SetDB(db *gorm.DB) *Migrate {
m.db = db m.db = db
return m return m
@ -83,15 +83,15 @@ func (m *Migrate) Rollback() error {
return errors.New("abnormal termination: first migration is nil") return errors.New("abnormal termination: first migration is nil")
} }
if err := m.GORMigrate.RollbackTo(m.first.ID); err == nil { if err := m.GORMigrate.RollbackTo(m.first.ID); err != nil {
if err := m.GORMigrate.RollbackMigration(m.first); err == nil { return err
}
if err := m.GORMigrate.RollbackMigration(m.first); err != nil {
return err
}
return nil return nil
} else {
return err
}
} else {
return err
}
} }
// MigrateTo specified version // MigrateTo specified version
@ -183,12 +183,12 @@ func (m *Migrate) Current() string {
return "0" return "0"
} }
if err := m.db.Last(&migrationInfo).Error; err == nil { if err := m.db.Last(&migrationInfo).Error; err != nil {
return migrationInfo.ID
} else {
fmt.Printf("warning => cannot fetch migration version: %s\n", err.Error()) fmt.Printf("warning => cannot fetch migration version: %s\n", err.Error())
return "0" return "0"
} }
return migrationInfo.ID
} }
// NextFrom returns next version from passed version // NextFrom returns next version from passed version
@ -197,9 +197,9 @@ func (m *Migrate) NextFrom(version string) (string, error) {
if ver == version { if ver == version {
if key < (len(m.versions) - 1) { if key < (len(m.versions) - 1) {
return m.versions[key+1], nil return m.versions[key+1], nil
} else {
return "", errors.New("this is last migration")
} }
return "", errors.New("this is last migration")
} }
} }
@ -212,9 +212,9 @@ func (m *Migrate) PreviousFrom(version string) (string, error) {
if ver == version { if ver == version {
if key > 0 { if key > 0 {
return m.versions[key-1], nil return m.versions[key-1], nil
} else {
return "0", nil
} }
return "0", nil
} }
} }

View File

@ -54,7 +54,7 @@ func (m *MigrateTest) RefreshMigrate() {
} }
} }
func (m *MigrateTest) Migration_TestModelFirst() *gormigrate.Migration { func (m *MigrateTest) MigrationTestModelFirst() *gormigrate.Migration {
return &gormigrate.Migration{ return &gormigrate.Migration{
ID: "1", ID: "1",
Migrate: func(db *gorm.DB) error { Migrate: func(db *gorm.DB) error {
@ -66,7 +66,7 @@ func (m *MigrateTest) Migration_TestModelFirst() *gormigrate.Migration {
} }
} }
func (m *MigrateTest) Migration_TestModelSecond() *gormigrate.Migration { func (m *MigrateTest) MigrationTestModelSecond() *gormigrate.Migration {
return &gormigrate.Migration{ return &gormigrate.Migration{
ID: "2", ID: "2",
Migrate: func(db *gorm.DB) error { Migrate: func(db *gorm.DB) error {
@ -81,7 +81,7 @@ func (m *MigrateTest) Migration_TestModelSecond() *gormigrate.Migration {
func (m *MigrateTest) Test_Add() { func (m *MigrateTest) Test_Add() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.Add(nil) m.Migrate.Add(nil)
m.Migrate.Add(m.Migration_TestModelFirst()) m.Migrate.Add(m.MigrationTestModelFirst())
assert.Equal(m.T(), 1, len(m.Migrate.migrations)) assert.Equal(m.T(), 1, len(m.Migrate.migrations))
i, ok := m.Migrate.migrations["1"] i, ok := m.Migrate.migrations["1"]
@ -112,7 +112,7 @@ func (m *MigrateTest) Test_prepareMigrations_AlreadyPrepared() {
func (m *MigrateTest) Test_prepareMigrations_OK() { func (m *MigrateTest) Test_prepareMigrations_OK() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.Add(m.Migration_TestModelFirst()) m.Migrate.Add(m.MigrationTestModelFirst())
err := m.Migrate.prepareMigrations() err := m.Migrate.prepareMigrations()
require.NoError(m.T(), err) require.NoError(m.T(), err)
@ -124,7 +124,7 @@ func (m *MigrateTest) Test_prepareMigrations_OK() {
func (m *MigrateTest) Test_Migrate_Fail_NilDB() { func (m *MigrateTest) Test_Migrate_Fail_NilDB() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.SetDB(nil) m.Migrate.SetDB(nil)
m.Migrate.Add(m.Migration_TestModelFirst()) m.Migrate.Add(m.MigrationTestModelFirst())
err := m.Migrate.Migrate() err := m.Migrate.Migrate()
@ -134,7 +134,7 @@ func (m *MigrateTest) Test_Migrate_Fail_NilDB() {
func (m *MigrateTest) Test_Migrate_Success_NoMigrations() { func (m *MigrateTest) Test_Migrate_Success_NoMigrations() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.Add(m.Migration_TestModelFirst()) m.Migrate.Add(m.MigrationTestModelFirst())
m.mock.ExpectBegin() m.mock.ExpectBegin()
m.mock. m.mock.
@ -157,7 +157,7 @@ func (m *MigrateTest) Test_Migrate_Success_NoMigrations() {
func (m *MigrateTest) Test_Migrate_Success() { func (m *MigrateTest) Test_Migrate_Success() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.Add(m.Migration_TestModelFirst()) m.Migrate.Add(m.MigrationTestModelFirst())
m.mock.ExpectBegin() m.mock.ExpectBegin()
m.mock. m.mock.
@ -188,7 +188,7 @@ func (m *MigrateTest) Test_Migrate_Success() {
func (m *MigrateTest) Test_Rollback_Fail_NilDB() { func (m *MigrateTest) Test_Rollback_Fail_NilDB() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.SetDB(nil) m.Migrate.SetDB(nil)
m.Migrate.Add(m.Migration_TestModelFirst()) m.Migrate.Add(m.MigrationTestModelFirst())
err := m.Migrate.Rollback() err := m.Migrate.Rollback()
@ -198,7 +198,7 @@ func (m *MigrateTest) Test_Rollback_Fail_NilDB() {
func (m *MigrateTest) Test_Rollback_Fail_NoMigrations() { func (m *MigrateTest) Test_Rollback_Fail_NoMigrations() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.first = m.Migration_TestModelFirst() m.Migrate.first = m.MigrationTestModelFirst()
err := m.Migrate.Rollback() err := m.Migrate.Rollback()
@ -208,7 +208,7 @@ func (m *MigrateTest) Test_Rollback_Fail_NoMigrations() {
func (m *MigrateTest) Test_Rollback_Fail_NoFirstMigration() { func (m *MigrateTest) Test_Rollback_Fail_NoFirstMigration() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.Add(m.Migration_TestModelFirst()) m.Migrate.Add(m.MigrationTestModelFirst())
m.Migrate.first = nil m.Migrate.first = nil
err := m.Migrate.Rollback() err := m.Migrate.Rollback()
@ -229,7 +229,7 @@ func (m *MigrateTest) Test_MigrateTo_Fail_NilDB() {
func (m *MigrateTest) Test_MigrateTo_DoNothing() { func (m *MigrateTest) Test_MigrateTo_DoNothing() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.Add(m.Migration_TestModelFirst()) m.Migrate.Add(m.MigrationTestModelFirst())
m.mock. m.mock.
ExpectExec(regexp.QuoteMeta(`CREATE TABLE "migrations" ("id" varchar(255) , PRIMARY KEY ("id"))`)). ExpectExec(regexp.QuoteMeta(`CREATE TABLE "migrations" ("id" varchar(255) , PRIMARY KEY ("id"))`)).
@ -247,7 +247,7 @@ func (m *MigrateTest) Test_MigrateTo_DoNothing() {
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1)) WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
m.mock.ExpectCommit() m.mock.ExpectCommit()
err := m.Migrate.MigrateTo(m.Migration_TestModelFirst().ID) err := m.Migrate.MigrateTo(m.MigrationTestModelFirst().ID)
assert.NoError(m.T(), err) assert.NoError(m.T(), err)
assert.NoError(m.T(), m.mock.ExpectationsWereMet()) assert.NoError(m.T(), m.mock.ExpectationsWereMet())
@ -255,7 +255,7 @@ func (m *MigrateTest) Test_MigrateTo_DoNothing() {
func (m *MigrateTest) Test_MigrateTo() { func (m *MigrateTest) Test_MigrateTo() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.Add(m.Migration_TestModelFirst()) m.Migrate.Add(m.MigrationTestModelFirst())
m.mock. m.mock.
ExpectExec(regexp.QuoteMeta(`CREATE TABLE "migrations" ("id" varchar(255) , PRIMARY KEY ("id"))`)). ExpectExec(regexp.QuoteMeta(`CREATE TABLE "migrations" ("id" varchar(255) , PRIMARY KEY ("id"))`)).
@ -280,7 +280,7 @@ func (m *MigrateTest) Test_MigrateTo() {
WillReturnResult(sqlmock.NewResult(1, 1)) WillReturnResult(sqlmock.NewResult(1, 1))
m.mock.ExpectCommit() m.mock.ExpectCommit()
err := m.Migrate.MigrateTo(m.Migration_TestModelFirst().ID) err := m.Migrate.MigrateTo(m.MigrationTestModelFirst().ID)
assert.NoError(m.T(), err) assert.NoError(m.T(), err)
assert.NoError(m.T(), m.mock.ExpectationsWereMet()) assert.NoError(m.T(), m.mock.ExpectationsWereMet())
@ -288,13 +288,13 @@ func (m *MigrateTest) Test_MigrateTo() {
func (m *MigrateTest) Test_RollbackTo() { func (m *MigrateTest) Test_RollbackTo() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.Add(m.Migration_TestModelFirst()) m.Migrate.Add(m.MigrationTestModelFirst())
m.Migrate.Add(m.Migration_TestModelSecond()) m.Migrate.Add(m.MigrationTestModelSecond())
m.mock.ExpectBegin() m.mock.ExpectBegin()
m.mock.ExpectCommit() m.mock.ExpectCommit()
err := m.Migrate.RollbackTo(m.Migration_TestModelSecond().ID) err := m.Migrate.RollbackTo(m.MigrationTestModelSecond().ID)
assert.NoError(m.T(), err) assert.NoError(m.T(), err)
assert.NoError(m.T(), m.mock.ExpectationsWereMet()) assert.NoError(m.T(), m.mock.ExpectationsWereMet())
@ -302,8 +302,8 @@ func (m *MigrateTest) Test_RollbackTo() {
func (m *MigrateTest) Test_MigrateNextTo() { func (m *MigrateTest) Test_MigrateNextTo() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.Add(m.Migration_TestModelFirst()) m.Migrate.Add(m.MigrationTestModelFirst())
m.Migrate.Add(m.Migration_TestModelSecond()) m.Migrate.Add(m.MigrationTestModelSecond())
m.mock. m.mock.
ExpectExec(regexp.QuoteMeta(`CREATE TABLE "migrations" ("id" varchar(255) , PRIMARY KEY ("id"))`)). ExpectExec(regexp.QuoteMeta(`CREATE TABLE "migrations" ("id" varchar(255) , PRIMARY KEY ("id"))`)).
@ -332,7 +332,7 @@ func (m *MigrateTest) Test_MigrateNextTo() {
WillReturnResult(sqlmock.NewResult(1, 1)) WillReturnResult(sqlmock.NewResult(1, 1))
m.mock.ExpectCommit() m.mock.ExpectCommit()
err := m.Migrate.MigrateNextTo(m.Migration_TestModelFirst().ID) err := m.Migrate.MigrateNextTo(m.MigrationTestModelFirst().ID)
assert.NoError(m.T(), err) assert.NoError(m.T(), err)
assert.NoError(m.T(), m.mock.ExpectationsWereMet()) assert.NoError(m.T(), m.mock.ExpectationsWereMet())
@ -340,14 +340,14 @@ func (m *MigrateTest) Test_MigrateNextTo() {
func (m *MigrateTest) Test_MigratePreviousTo() { func (m *MigrateTest) Test_MigratePreviousTo() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.Add(m.Migration_TestModelFirst()) m.Migrate.Add(m.MigrationTestModelFirst())
m.Migrate.Add(m.Migration_TestModelSecond()) m.Migrate.Add(m.MigrationTestModelSecond())
m.mock. m.mock.
ExpectExec(regexp.QuoteMeta(`CREATE TABLE "migrations" ("id" varchar(255) , PRIMARY KEY ("id"))`)). ExpectExec(regexp.QuoteMeta(`CREATE TABLE "migrations" ("id" varchar(255) , PRIMARY KEY ("id"))`)).
WillReturnResult(sqlmock.NewResult(1, 1)) WillReturnResult(sqlmock.NewResult(1, 1))
err := m.Migrate.MigratePreviousTo(m.Migration_TestModelSecond().ID) err := m.Migrate.MigratePreviousTo(m.MigrationTestModelSecond().ID)
assert.Error(m.T(), err) assert.Error(m.T(), err)
assert.NoError(m.T(), m.mock.ExpectationsWereMet()) assert.NoError(m.T(), m.mock.ExpectationsWereMet())

View File

@ -35,6 +35,7 @@ type NewMigrationCommand struct {
Directory string `short:"d" long:"directory" default:"./migrations" description:"Directory where migration will be created"` Directory string `short:"d" long:"directory" default:"./migrations" description:"Directory where migration will be created"`
} }
// FileExists returns true if provided file exist and it's not directory
func (x *NewMigrationCommand) FileExists(filename string) bool { func (x *NewMigrationCommand) FileExists(filename string) bool {
info, err := os.Stat(filename) info, err := os.Stat(filename)
if os.IsNotExist(err) { if os.IsNotExist(err) {
@ -43,6 +44,7 @@ func (x *NewMigrationCommand) FileExists(filename string) bool {
return !info.IsDir() return !info.IsDir()
} }
// Execute migration generator command
func (x *NewMigrationCommand) Execute(args []string) error { func (x *NewMigrationCommand) Execute(args []string) error {
version := strconv.FormatInt(time.Now().Unix(), 10) version := strconv.FormatInt(time.Now().Unix(), 10)
directory := path.Clean(x.Directory) directory := path.Clean(x.Directory)

View File

@ -42,7 +42,7 @@ func (s *MigrationGeneratorSuite) Test_Execute() {
} }
for _, f := range files { for _, f := range files {
if strings.Index(f.Name(), "_app.go") != -1 { if strings.Contains(f.Name(), "_app.go") {
found = true found = true
assert.NoError(s.T(), os.Remove(path.Join(s.command.Directory, f.Name()))) assert.NoError(s.T(), os.Remove(path.Join(s.command.Directory, f.Name())))
} }

View File

@ -25,7 +25,7 @@ func NewStaticRenderer(funcMap template.FuncMap) Renderer {
return newRendererWithMultitemplate(funcMap, multitemplate.New()) return newRendererWithMultitemplate(funcMap, multitemplate.New())
} }
// NewStaticRenderer is a Renderer constructor with multitemplate.DynamicRender // NewDynamicRenderer is a Renderer constructor with multitemplate.DynamicRender
func NewDynamicRenderer(funcMap template.FuncMap) Renderer { func NewDynamicRenderer(funcMap template.FuncMap) Renderer {
return newRendererWithMultitemplate(funcMap, multitemplate.NewDynamic()) return newRendererWithMultitemplate(funcMap, multitemplate.NewDynamic())
} }
@ -47,9 +47,9 @@ func (r *Renderer) Push(name string, files ...string) *template.Template {
if r.TemplatesBox == nil { if r.TemplatesBox == nil {
return r.storeTemplate(name, r.AddFromFilesFuncs(name, r.FuncMap, files...)) return r.storeTemplate(name, r.AddFromFilesFuncs(name, r.FuncMap, files...))
} else {
return r.storeTemplate(name, r.addFromBox(name, r.FuncMap, files...))
} }
return r.storeTemplate(name, r.addFromBox(name, r.FuncMap, files...))
} }
// addFromBox adds embedded template // addFromBox adds embedded template
@ -67,7 +67,7 @@ func (r *Renderer) addFromBox(name string, funcMap template.FuncMap, files ...st
// storeTemplate stores built template if multitemplate.DynamicRender is used. // storeTemplate stores built template if multitemplate.DynamicRender is used.
// Dynamic render doesn't store templates - it stores builders, that's why we can't just extract them. // Dynamic render doesn't store templates - it stores builders, that's why we can't just extract them.
// It possibly can cause data inconsistency in developer enviroments where return value from Renderer.Push is used. // It possibly can cause data inconsistency in developer environments where return value from Renderer.Push is used.
func (r *Renderer) storeTemplate(name string, tpl *template.Template) *template.Template { func (r *Renderer) storeTemplate(name string, tpl *template.Template) *template.Template {
if _, ok := r.Renderer.(multitemplate.DynamicRender); ok { if _, ok := r.Renderer.(multitemplate.DynamicRender); ok {
r.alreadyAdded[name] = tpl r.alreadyAdded[name] = tpl

View File

@ -23,7 +23,7 @@ type TranslationsExtractor struct {
TranslationsPath string TranslationsPath string
} }
// TranslationsExtractor constructor. Use "translate.{}.yml" as template if your translations are named like "translate.en.yml" // NewTranslationsExtractor constructor. Use "translate.{}.yml" as template if your translations are named like "translate.en.yml"
func NewTranslationsExtractor(fileNameTemplate string) *TranslationsExtractor { func NewTranslationsExtractor(fileNameTemplate string) *TranslationsExtractor {
return &TranslationsExtractor{fileNameTemplate: fileNameTemplate} return &TranslationsExtractor{fileNameTemplate: fileNameTemplate}
} }
@ -32,46 +32,60 @@ func NewTranslationsExtractor(fileNameTemplate string) *TranslationsExtractor {
func (t *TranslationsExtractor) unmarshalToMap(in []byte) (map[string]interface{}, error) { func (t *TranslationsExtractor) unmarshalToMap(in []byte) (map[string]interface{}, error) {
var dataMap map[string]interface{} var dataMap map[string]interface{}
if err := yaml.Unmarshal(in, &dataMap); err == nil { if err := yaml.Unmarshal(in, &dataMap); err != nil {
return dataMap, nil
} else {
return dataMap, err return dataMap, err
} }
return dataMap, nil
} }
// loadYAMLBox loads YAML from box // loadYAMLBox loads YAML from box
func (t *TranslationsExtractor) loadYAMLBox(fileName string) (map[string]interface{}, error) { func (t *TranslationsExtractor) loadYAMLBox(fileName string) (map[string]interface{}, error) {
var dataMap map[string]interface{} var (
dataMap map[string]interface{}
data []byte
err error
)
if data, err := t.TranslationsBox.Find(fileName); err == nil { if data, err = t.TranslationsBox.Find(fileName); err != nil {
return t.unmarshalToMap(data)
} else {
return dataMap, err return dataMap, err
} }
return t.unmarshalToMap(data)
} }
// loadYAMLFile loads YAML from file // loadYAMLFile loads YAML from file
func (t *TranslationsExtractor) loadYAMLFile(fileName string) (map[string]interface{}, error) { func (t *TranslationsExtractor) loadYAMLFile(fileName string) (map[string]interface{}, error) {
var dataMap map[string]interface{} var (
dataMap map[string]interface{}
info os.FileInfo
err error
)
if info, err := os.Stat(fileName); err == nil { if info, err = os.Stat(fileName); err == nil {
if !info.IsDir() { if !info.IsDir() {
if path, err := filepath.Abs(fileName); err == nil { var (
if source, err := ioutil.ReadFile(path); err == nil { path string
source []byte
err error
)
if path, err = filepath.Abs(fileName); err != nil {
return dataMap, err
}
if source, err = ioutil.ReadFile(path); err != nil {
return dataMap, err
}
return t.unmarshalToMap(source) return t.unmarshalToMap(source)
} else {
return dataMap, err
} }
} else {
return dataMap, err
}
} else {
return dataMap, errors.New("directory provided instead of file") return dataMap, errors.New("directory provided instead of file")
} }
} else {
return dataMap, err return dataMap, err
} }
}
// loadYAML loads YAML from filesystem or from packr box - depends on what was configured. Can return error. // loadYAML loads YAML from filesystem or from packr box - depends on what was configured. Can return error.
func (t *TranslationsExtractor) loadYAML(fileName string) (map[string]interface{}, error) { func (t *TranslationsExtractor) loadYAML(fileName string) (map[string]interface{}, error) {
@ -106,9 +120,13 @@ func (t *TranslationsExtractor) LoadLocale(locale string) (map[string]interface{
// LoadLocaleKeys returns only sorted keys from translation file // LoadLocaleKeys returns only sorted keys from translation file
func (t *TranslationsExtractor) LoadLocaleKeys(locale string) ([]string, error) { func (t *TranslationsExtractor) LoadLocaleKeys(locale string) ([]string, error) {
if data, err := t.LoadLocale(locale); err == nil { var (
return t.GetMapKeys(data), nil data map[string]interface{}
} else { err error
)
if data, err = t.LoadLocale(locale); err != nil {
return []string{}, err return []string{}, err
} }
return t.GetMapKeys(data), nil
} }

View File

@ -38,6 +38,8 @@ func (t *TranslationsExtractorTest) SetupSuite() {
"another": "second", "another": "second",
} }
data, _ := yaml.Marshal(translation) data, _ := yaml.Marshal(translation)
// It's not regular temporary file. Little hack in order to test translations extractor.
// nolint:gosec
errWrite := ioutil.WriteFile("/tmp/translate.en.yml", data, os.ModePerm) errWrite := ioutil.WriteFile("/tmp/translate.en.yml", data, os.ModePerm)
require.NoError(t.T(), errWrite) require.NoError(t.T(), errWrite)

View File

@ -1,6 +1,7 @@
package core package core
import ( import (
// nolint:gosec
"crypto/sha1" "crypto/sha1"
"crypto/sha256" "crypto/sha256"
"encoding/json" "encoding/json"
@ -24,9 +25,9 @@ import (
// Utils service object // Utils service object
type Utils struct { type Utils struct {
IsDebug bool IsDebug bool
TokenCounter uint32
ConfigAWS ConfigAWS ConfigAWS ConfigAWS
Logger *logging.Logger Logger *logging.Logger
TokenCounter uint32
slashRegex *regexp.Regexp slashRegex *regexp.Regexp
} }
@ -127,6 +128,7 @@ func (u *Utils) UploadUserAvatar(url string) (picURLs3 string, err error) {
s := session.Must(session.NewSession(s3Config)) s := session.Must(session.NewSession(s3Config))
uploader := s3manager.NewUploader(s) uploader := s3manager.NewUploader(s)
// nolint:gosec
resp, err := http.Get(url) resp, err := http.Get(url)
if err != nil { if err != nil {
return return
@ -181,6 +183,7 @@ func GetMGItemData(client *v1.MgClient, url string, caption string) (v1.Item, in
func GetEntitySHA1(v interface{}) (hash string, err error) { func GetEntitySHA1(v interface{}) (hash string, err error) {
res, _ := json.Marshal(v) res, _ := json.Marshal(v)
// nolint:gosec
h := sha1.New() h := sha1.New()
_, err = h.Write(res) _, err = h.Write(res)
hash = fmt.Sprintf("%x", h.Sum(nil)) hash = fmt.Sprintf("%x", h.Sum(nil))

View File

@ -1,12 +1,11 @@
package core package core
import ( import (
"reflect"
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding"
"gopkg.in/go-playground/validator.v8" "gopkg.in/go-playground/validator.v9"
) )
// init here will register `validatecrmurl` function for gin validator
func init() { func init() {
if v, ok := binding.Validator.Engine().(*validator.Validate); ok { if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
if err := v.RegisterValidation("validatecrmurl", validateCrmURL); err != nil { if err := v.RegisterValidation("validatecrmurl", validateCrmURL); err != nil {
@ -15,9 +14,7 @@ func init() {
} }
} }
func validateCrmURL( // validateCrmURL will validate CRM URL
v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value, func validateCrmURL(fl validator.FieldLevel) bool {
field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string, return regCommandName.Match([]byte(fl.Field().String()))
) bool {
return regCommandName.Match([]byte(field.Interface().(string)))
} }

63
core/validator_test.go Normal file
View File

@ -0,0 +1,63 @@
package core
import (
"testing"
"github.com/gin-gonic/gin/binding"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"gopkg.in/go-playground/validator.v9"
)
type ValidatorSuite struct {
suite.Suite
engine *validator.Validate
}
func Test_Validator(t *testing.T) {
suite.Run(t, new(ValidatorSuite))
}
func (s *ValidatorSuite) SetupSuite() {
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
s.engine = v
} else {
s.T().Fatal("cannot obtain validation engine")
}
}
func (s *ValidatorSuite) getError(err error) string {
if err == nil {
return ""
}
return err.Error()
}
func (s *ValidatorSuite) Test_ValidationInvalidType() {
assert.IsType(s.T(), &validator.InvalidValidationError{}, s.engine.Struct(nil))
}
func (s *ValidatorSuite) Test_ValidationFails() {
conn := Connection{
Key: "key",
URL: "url",
}
err := s.engine.Struct(conn)
require.IsType(s.T(), validator.ValidationErrors{}, err)
validatorErrors := err.(validator.ValidationErrors)
assert.Equal(
s.T(),
"Key: 'Connection.URL' Error:Field validation for 'URL' failed on the 'validatecrmurl' tag",
validatorErrors.Error())
}
func (s *ValidatorSuite) Test_ValidationSuccess() {
conn := Connection{
Key: "key",
URL: "https://test.retailcrm.pro",
}
err := s.engine.Struct(conn)
assert.NoError(s.T(), err, s.getError(err))
}

17
go.mod
View File

@ -9,20 +9,20 @@ require (
github.com/denisenkom/go-mssqldb v0.0.0-20190830225923-3302f0226fbd // indirect github.com/denisenkom/go-mssqldb v0.0.0-20190830225923-3302f0226fbd // indirect
github.com/getsentry/raven-go v0.2.0 github.com/getsentry/raven-go v0.2.0
github.com/gin-contrib/multitemplate v0.0.0-20190914010127-bba2ccfe37ec github.com/gin-contrib/multitemplate v0.0.0-20190914010127-bba2ccfe37ec
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-gonic/gin v1.5.0
github.com/gin-gonic/gin v1.4.0 github.com/go-playground/universal-translator v0.17.0 // indirect
github.com/gobuffalo/packd v0.3.0 github.com/gobuffalo/packd v0.3.0
github.com/gobuffalo/packr/v2 v2.7.1 github.com/gobuffalo/packr/v2 v2.7.1
github.com/golang/protobuf v1.3.2 // indirect
github.com/google/go-querystring v1.0.0 // indirect github.com/google/go-querystring v1.0.0 // indirect
github.com/gorilla/securecookie v1.1.1 github.com/gorilla/securecookie v1.1.1
github.com/gorilla/sessions v1.2.0 github.com/gorilla/sessions v1.2.0
github.com/h2non/gock v1.0.10 github.com/h2non/gock v1.0.10
github.com/jessevdk/go-flags v1.4.0 github.com/jessevdk/go-flags v1.4.0
github.com/jinzhu/gorm v1.9.11 github.com/jinzhu/gorm v1.9.11
github.com/json-iterator/go v1.1.7 // indirect github.com/json-iterator/go v1.1.8 // indirect
github.com/leodido/go-urn v1.2.0 // indirect
github.com/lib/pq v1.2.0 // indirect github.com/lib/pq v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.10 // indirect github.com/mattn/go-isatty v0.0.11 // indirect
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 // indirect github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 // indirect
github.com/nicksnyder/go-i18n/v2 v2.0.2 github.com/nicksnyder/go-i18n/v2 v2.0.2
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
@ -31,12 +31,11 @@ require (
github.com/retailcrm/mg-transport-api-client-go v1.1.32 github.com/retailcrm/mg-transport-api-client-go v1.1.32
github.com/rogpeppe/go-internal v1.5.0 // indirect github.com/rogpeppe/go-internal v1.5.0 // indirect
github.com/stretchr/testify v1.4.0 github.com/stretchr/testify v1.4.0
github.com/ugorji/go v1.1.7 // indirect
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 // indirect golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 // indirect
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 // indirect
golang.org/x/text v0.3.2 golang.org/x/text v0.3.2
gopkg.in/go-playground/validator.v8 v8.18.2 gopkg.in/go-playground/validator.v9 v9.30.2
gopkg.in/gormigrate.v1 v1.6.0 gopkg.in/gormigrate.v1 v1.6.0
gopkg.in/yaml.v2 v2.2.4 gopkg.in/yaml.v2 v2.2.7
) )

30
go.sum
View File

@ -48,8 +48,18 @@ 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-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ= github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/gin-gonic/gin v1.5.0 h1:fi+bqFAx/oLK54somfCtEZs9HeH1LHVoEPUgARpTqyc=
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 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-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc=
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM=
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= 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-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/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@ -114,6 +124,8 @@ github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqx
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 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/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/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@ -126,14 +138,21 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= 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/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= 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/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
@ -259,9 +278,13 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ=
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 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.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 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
@ -296,8 +319,11 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
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/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc=
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/go-playground/validator.v9 v9.30.2 h1:icxYLlYflpazIV3ufMoNB9h9SYMQ37DZ8CTwkU4pnOs=
gopkg.in/go-playground/validator.v9 v9.30.2/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/gormigrate.v1 v1.6.0 h1:XpYM6RHQPmzwY7Uyu+t+xxMXc86JYFJn4nEc9HzQjsI= gopkg.in/gormigrate.v1 v1.6.0 h1:XpYM6RHQPmzwY7Uyu+t+xxMXc86JYFJn4nEc9HzQjsI=
gopkg.in/gormigrate.v1 v1.6.0/go.mod h1:Lf00lQrHqfSYWiTtPcyQabsDdM6ejZaMgV0OU6JMSlw= gopkg.in/gormigrate.v1 v1.6.0/go.mod h1:Lf00lQrHqfSYWiTtPcyQabsDdM6ejZaMgV0OU6JMSlw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
@ -306,6 +332,8 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/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-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-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=