Compare commits

..

1 Commits

34 changed files with 647 additions and 913 deletions

1
.gitignore vendored
View File

@ -20,4 +20,3 @@ docs/resources/
pkg/static/templates_vfsdata.go
files/
!pkg/files/
vikunja-dump*

View File

@ -218,57 +218,3 @@ services:
- frontend
restart: unless-stopped
{{< /highlight >}}
## Example with Caddy v2 as proxy
You will need the following `Caddyfile` on your host (or elsewhere, but then you'd need to adjust the proxy mount at the bottom of the compose file):
{{< highlight conf >}}
vikunja.example.com {
reverse_proxy /api/* api:3456
reverse_proxy frontend:80
}
{{< /highlight >}}
`docker-compose.yml` config:
{{< highlight yaml >}}
version: '3'
services:
db:
image: mariadb:10
environment:
MYSQL_ROOT_PASSWORD: supersecret
MYSQL_DATABASE: vikunja
volumes:
- ./db:/var/lib/mysql
restart: unless-stopped
api:
image: vikunja/api
environment:
VIKUNJA_DATABASE_HOST: db
VIKUNJA_DATABASE_PASSWORD: supersecret
VIKUNJA_DATABASE_TYPE: mysql
VIKUNJA_DATABASE_USER: root
VIKUNJA_DATABASE_DATABASE: vikunja
volumes:
- ./files:/app/vikunja/files
depends_on:
- db
restart: unless-stopped
frontend:
image: vikunja/frontend
restart: unless-stopped
caddy:
image: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
depends_on:
- api
- frontend
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
{{< /highlight >}}

View File

@ -81,23 +81,4 @@ Starts Vikunja's REST api server.
Usage:
{{< highlight bash >}}
$ vikunja web
{{< /highlight >}}
### `dump`
Creates a zip file with all vikunja-related files.
This includes config, version, all files and the full database.
Usage:
{{< highlight bash >}}
$ vikunja dump
{{< /highlight >}}
### `restore`
Restores a previously created dump from a zip file, see `dump`.
Usage:
{{< highlight bash >}}
$ vikunja restore <path to dump zip file>
{{< /highlight >}}
{{< /highlight >}}

2
docs/themes/vikunja vendored

@ -1 +1 @@
Subproject commit f50566db25df9fa03243ba06d17511e050d4be95
Subproject commit 7dce4df7b6068137d3e6127693706e806fc264b1

2
go.mod
View File

@ -22,7 +22,7 @@ require (
code.vikunja.io/web v0.0.0-20200618164749-a5f3d450d39a
gitea.com/xorm/xorm-redis-cache v0.2.0
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535
github.com/beevik/etree v1.1.0 // indirect
github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee
github.com/client9/misspell v0.3.4

2
go.sum
View File

@ -65,6 +65,8 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY=
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=

View File

@ -17,6 +17,15 @@
package cmd
import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/mail"
"code.vikunja.io/api/pkg/migration"
"code.vikunja.io/api/pkg/models"
migrator "code.vikunja.io/api/pkg/modules/migration"
"code.vikunja.io/api/pkg/red"
"code.vikunja.io/api/pkg/user"
"fmt"
"github.com/spf13/cobra"
"os"
@ -45,3 +54,48 @@ func Execute() {
os.Exit(1)
}
}
// Will only fullInit config, redis, logger but no db connection.
func lightInit() {
// Init the config
config.InitConfig()
// Init redis
red.InitRedis()
// Set logger
log.InitLogger()
}
// Initializes all kinds of things in the right order
func fullInit() {
lightInit()
// Run the migrations
migration.Migrate(nil)
// Set Engine
err := models.SetEngine()
if err != nil {
log.Fatal(err.Error())
}
err = user.InitDB()
if err != nil {
log.Fatal(err.Error())
}
err = files.SetEngine()
if err != nil {
log.Fatal(err.Error())
}
err = migrator.InitDB()
if err != nil {
log.Fatal(err.Error())
}
// Initialize the files handler
files.InitFileHandler()
// Start the mail daemon
mail.StartMailDaemon()
}

View File

@ -1,43 +0,0 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (
"code.vikunja.io/api/pkg/initialize"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/modules/dump"
"github.com/spf13/cobra"
"time"
)
func init() {
rootCmd.AddCommand(dumpCmd)
}
var dumpCmd = &cobra.Command{
Use: "dump",
Short: "Dump all vikunja data into a zip file. Includes config, files and db.",
PreRun: func(cmd *cobra.Command, args []string) {
initialize.FullInit()
},
Run: func(cmd *cobra.Command, args []string) {
filename := "vikunja-dump_" + time.Now().Format("2006-01-02_15-03-05") + ".zip"
if err := dump.Dump(filename); err != nil {
log.Critical(err.Error())
}
},
}

View File

@ -17,7 +17,6 @@
package cmd
import (
"code.vikunja.io/api/pkg/initialize"
"code.vikunja.io/api/pkg/migration"
"github.com/spf13/cobra"
)
@ -37,7 +36,7 @@ var migrateCmd = &cobra.Command{
Use: "migrate",
Short: "Run all database migrations which didn't already run.",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
initialize.LightInit()
lightInit()
},
Run: func(cmd *cobra.Command, args []string) {
migration.Migrate(nil)

View File

@ -1,42 +0,0 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (
"code.vikunja.io/api/pkg/initialize"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/modules/dump"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(restoreCmd)
}
var restoreCmd = &cobra.Command{
Use: "restore [filename]",
Short: "Restores all vikunja data from a vikunja dump.",
Args: cobra.ExactArgs(1),
PreRun: func(cmd *cobra.Command, args []string) {
initialize.FullInit()
},
Run: func(cmd *cobra.Command, args []string) {
if err := dump.Restore(args[0]); err != nil {
log.Critical(err.Error())
}
},
}

View File

@ -17,7 +17,6 @@
package cmd
import (
"code.vikunja.io/api/pkg/initialize"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/mail"
"github.com/spf13/cobra"
@ -32,7 +31,7 @@ var testmailCmd = &cobra.Command{
Short: "Send a test mail using the configured smtp connection",
Args: cobra.ExactArgs(1),
PreRun: func(cmd *cobra.Command, args []string) {
initialize.LightInit()
lightInit()
// Start the mail daemon
mail.StartMailDaemon()

View File

@ -18,7 +18,6 @@ package cmd
import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/initialize"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/routes"
"code.vikunja.io/api/pkg/swagger"
@ -38,7 +37,7 @@ var webCmd = &cobra.Command{
Use: "web",
Short: "Starts the rest api web server",
PreRun: func(cmd *cobra.Command, args []string) {
initialize.FullInit()
fullInit()
},
Run: func(cmd *cobra.Command, args []string) {

View File

@ -168,20 +168,3 @@ func initSqliteEngine() (engine *xorm.Engine, err error) {
return xorm.NewEngine("sqlite3", path)
}
// WipeEverything wipes all tables and their data. Use with caution...
func WipeEverything() error {
tables, err := x.DBMetas()
if err != nil {
return err
}
for _, t := range tables {
if err := x.DropTables(t.Name); err != nil {
return err
}
}
return nil
}

View File

@ -1,54 +0,0 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package db
import "encoding/json"
// Dump dumps all database tables
func Dump() (data map[string][]byte, err error) {
tables, err := x.DBMetas()
if err != nil {
return
}
data = make(map[string][]byte, len(tables))
for _, table := range tables {
entries := []map[string]interface{}{}
err := x.Table(table.Name).Find(&entries)
if err != nil {
return nil, err
}
data[table.Name], err = json.Marshal(entries)
if err != nil {
return nil, err
}
}
return
}
// Restore restores a table with all its entries
func Restore(table string, contents []map[string]interface{}) (err error) {
for _, content := range contents {
if _, err := x.Table(table).Insert(content); err != nil {
return err
}
}
return
}

View File

@ -1,41 +0,0 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package files
import (
"io"
)
// Dump dumps all saved files
// This only includes the raw files, no db entries.
func Dump() (allFiles map[int64]io.ReadCloser, err error) {
files := []*File{}
err = x.Find(&files)
if err != nil {
return
}
allFiles = make(map[int64]io.ReadCloser, len(files))
for _, file := range files {
if err := file.LoadFileByID(); err != nil {
return nil, err
}
allFiles[file.ID] = file.File
}
return
}

View File

@ -96,7 +96,7 @@ func Create(f io.ReadCloser, realname string, realsize uint64, a web.Auth) (file
}
// Save the file to storage with its new ID as path
err = file.Save(f)
err = afs.WriteReader(file.getFileName(), f)
return
}
@ -113,8 +113,3 @@ func (f *File) Delete() (err error) {
err = afs.Remove(f.getFileName())
return
}
// Save saves a file to storage
func (f *File) Save(fcontent io.ReadCloser) error {
return afs.WriteReader(f.getFileName(), fcontent)
}

View File

@ -1,79 +0,0 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package initialize
import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/mail"
"code.vikunja.io/api/pkg/migration"
"code.vikunja.io/api/pkg/models"
migrator "code.vikunja.io/api/pkg/modules/migration"
"code.vikunja.io/api/pkg/red"
"code.vikunja.io/api/pkg/user"
)
// LightInit will only fullInit config, redis, logger but no db connection.
func LightInit() {
// Init the config
config.InitConfig()
// Init redis
red.InitRedis()
// Set logger
log.InitLogger()
}
// InitEngines intializes all db connections
func InitEngines() {
err := models.SetEngine()
if err != nil {
log.Fatal(err.Error())
}
err = user.InitDB()
if err != nil {
log.Fatal(err.Error())
}
err = files.SetEngine()
if err != nil {
log.Fatal(err.Error())
}
err = migrator.InitDB()
if err != nil {
log.Fatal(err.Error())
}
}
// FullInit initializes all kinds of things in the right order
func FullInit() {
LightInit()
// Run the migrations
migration.Migrate(nil)
// Set Engine
InitEngines()
// Initialize the files handler
files.InitFileHandler()
// Start the mail daemon
mail.StartMailDaemon()
}

View File

@ -104,15 +104,6 @@ func Rollback(migrationID string) {
log.Info("Rolled back successfully.")
}
// MigrateTo executes all migrations up to a certain point
func MigrateTo(migrationID string, x *xorm.Engine) error {
m := initMigration(x)
if err := m.MigrateTo(migrationID); err != nil {
return err
}
return nil
}
// Deletes a column from a table. All arguments are strings, to let them be standalone and not depending on any struct.
func dropTableColum(x *xorm.Engine, tableName, col string) error {

View File

@ -1,146 +0,0 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package dump
import (
"archive/zip"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/version"
"fmt"
"github.com/spf13/viper"
"io"
"os"
"strconv"
)
// Change to deflate to gain better compression
// see http://golang.org/pkg/archive/zip/#pkg-constants
const compressionUsed = zip.Deflate
// Dump creates a zip file with all vikunja files at filename
func Dump(filename string) error {
dumpFile, err := os.Create(filename)
if err != nil {
return fmt.Errorf("error opening dump file: %s", err)
}
defer dumpFile.Close()
dumpWriter := zip.NewWriter(dumpFile)
defer dumpWriter.Close()
// Config
log.Info("Start dumping config file...")
err = writeFileToZip(viper.ConfigFileUsed(), dumpWriter)
if err != nil {
return fmt.Errorf("error saving config file: %s", err)
}
log.Info("Dumped config file")
// Version
log.Info("Start dumping version file...")
err = writeBytesToZip("VERSION", []byte(version.Version), dumpWriter)
if err != nil {
return fmt.Errorf("error saving version: %s", err)
}
log.Info("Dumped version")
// Database
log.Info("Start dumping database...")
data, err := db.Dump()
if err != nil {
return fmt.Errorf("error saving database data: %s", err)
}
for t, d := range data {
err = writeBytesToZip("database/"+t+".json", d, dumpWriter)
if err != nil {
return fmt.Errorf("error writing database table %s: %s", t, err)
}
}
log.Info("Dumped database")
// Files
log.Info("Start dumping files...")
allFiles, err := files.Dump()
if err != nil {
return fmt.Errorf("error saving file: %s", err)
}
for fid, file := range allFiles {
header := &zip.FileHeader{
Name: "files/" + strconv.FormatInt(fid, 10),
Method: compressionUsed,
}
w, err := dumpWriter.CreateHeader(header)
if err != nil {
return err
}
_, err = io.Copy(w, file)
if err != nil {
return fmt.Errorf("error writing file %d: %s", fid, err)
}
_ = file.Close()
}
log.Infof("Dumped files")
log.Info("Done creating dump")
log.Infof("Dump file saved at %s", filename)
return nil
}
func writeFileToZip(filename string, writer *zip.Writer) error {
// #nosec
fileToZip, err := os.Open(filename)
if err != nil {
return err
}
defer fileToZip.Close()
// Get the file information
info, err := fileToZip.Stat()
if err != nil {
return err
}
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
header.Name = info.Name()
header.Method = compressionUsed
w, err := writer.CreateHeader(header)
if err != nil {
return err
}
_, err = io.Copy(w, fileToZip)
return err
}
func writeBytesToZip(filename string, data []byte, writer *zip.Writer) (err error) {
header := &zip.FileHeader{
Name: filename,
Method: compressionUsed,
}
w, err := writer.CreateHeader(header)
if err != nil {
return err
}
_, err = w.Write(data)
return
}

View File

@ -1,217 +0,0 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package dump
import (
"archive/zip"
"bufio"
"bytes"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/initialize"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/migration"
"encoding/json"
"fmt"
"io"
"os"
"sort"
"src.techknowlogick.com/xormigrate"
"strconv"
"strings"
)
const maxConfigSize = 5 * 1024 * 1024 // 5 MB, should be largely enough
// Restore takes a zip file name and restores it
func Restore(filename string) error {
r, err := zip.OpenReader(filename)
if err != nil {
return fmt.Errorf("could not open zip file: %s", err)
}
log.Warning("Restoring a dump will wipe your current installation!")
log.Warning("To confirm, please type 'Yes, I understand' and confirm with enter:")
cr := bufio.NewReader(os.Stdin)
text, err := cr.ReadString('\n')
if err != nil {
return fmt.Errorf("could not read confirmation message: %s", err)
}
if text != "Yes, I understand\n" {
return fmt.Errorf("invalid confirmation message")
}
// Find the configFile, database and files files
var configFile *zip.File
dbfiles := make(map[string]*zip.File)
filesFiles := make(map[string]*zip.File)
for _, file := range r.File {
if strings.HasPrefix(file.Name, "config") {
configFile = file
continue
}
if strings.HasPrefix(file.Name, "database/") {
fname := strings.ReplaceAll(file.Name, "database/", "")
dbfiles[fname[:len(fname)-5]] = file
continue
}
if strings.HasPrefix(file.Name, "files/") {
filesFiles[strings.ReplaceAll(file.Name, "files/", "")] = file
}
}
if configFile == nil {
return fmt.Errorf("dump does not contain a config file")
}
///////
// Restore the config file
if configFile.UncompressedSize64 > maxConfigSize {
return fmt.Errorf("config file too large, is %d, max size is %d", configFile.UncompressedSize64, maxConfigSize)
}
outFile, err := os.OpenFile(configFile.Name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, configFile.Mode())
if err != nil {
return fmt.Errorf("could not open config file for writing: %s", err)
}
cfgr, err := configFile.Open()
if err != nil {
return err
}
// #nosec - We eliminated the potential decompression bomb by erroring out above if the file is larger than a threshold.
_, err = io.Copy(outFile, cfgr)
if err != nil {
return fmt.Errorf("could not create config file: %s", err)
}
_ = cfgr.Close()
_ = outFile.Close()
log.Infof("The config file has been restored to '%s'.", configFile.Name)
log.Infof("You can now make changes to it, hit enter when you're done.")
if _, err := bufio.NewReader(os.Stdin).ReadString('\n'); err != nil {
return fmt.Errorf("could not read from stdin: %s", err)
}
log.Info("Restoring...")
// Init the configFile again since the restored configuration is most likely different from the one before
initialize.LightInit()
initialize.InitEngines()
files.InitFileHandler()
///////
// Restore the db
// Start by wiping everything
if err := db.WipeEverything(); err != nil {
return fmt.Errorf("could not wipe database: %s", err)
}
log.Info("Wiped database.")
// Because we don't explicitly saved the table definitions, we take the last ran db migration from the dump
// and execute everything until that point.
migrations := dbfiles["migration"]
rc, err := migrations.Open()
if err != nil {
return fmt.Errorf("could not open migrations: %s", err)
}
defer rc.Close()
var buf bytes.Buffer
if _, err := buf.ReadFrom(rc); err != nil {
return fmt.Errorf("could not read migrations: %s", err)
}
ms := []*xormigrate.Migration{}
if err := json.Unmarshal(buf.Bytes(), &ms); err != nil {
return fmt.Errorf("could not read migrations: %s", err)
}
sort.Slice(ms, func(i, j int) bool {
return ms[i].ID > ms[j].ID
})
lastMigration := ms[len(ms)-1]
if err := migration.MigrateTo(lastMigration.ID, nil); err != nil {
return fmt.Errorf("could not create db structure: %s", err)
}
// Restore all db data
for table, d := range dbfiles {
content, err := unmarshalFileToJSON(d)
if err != nil {
return fmt.Errorf("could not read table %s: %s", table, err)
}
if err := db.Restore(table, content); err != nil {
return fmt.Errorf("could not restore table data for table %s: %s", table, err)
}
log.Infof("Restored table %s", table)
}
log.Infof("Restored %d tables", len(dbfiles))
// Run migrations again to migrate a potentially outdated dump
migration.Migrate(nil)
///////
// Restore Files
for i, file := range filesFiles {
id, err := strconv.ParseInt(i, 10, 64)
if err != nil {
return fmt.Errorf("could not parse file id %s: %s", i, err)
}
f := &files.File{ID: id}
fc, err := file.Open()
if err != nil {
return fmt.Errorf("could not open file %s: %s", i, err)
}
if err := f.Save(fc); err != nil {
return fmt.Errorf("could not save file: %s", err)
}
_ = fc.Close()
log.Infof("Restored file %s", i)
}
log.Infof("Restored %d files.", len(filesFiles))
///////
// Done
log.Infof("Done restoring dump.")
return nil
}
func unmarshalFileToJSON(file *zip.File) (contents []map[string]interface{}, err error) {
rc, err := file.Open()
if err != nil {
return
}
defer rc.Close()
var buf bytes.Buffer
if _, err := buf.ReadFrom(rc); err != nil {
return nil, err
}
contents = []map[string]interface{}{}
if err := json.Unmarshal(buf.Bytes(), &contents); err != nil {
return nil, err
}
return
}

View File

@ -45,9 +45,6 @@ func RegisterUser(c echo.Context) error {
if err := c.Bind(&datUser); err != nil {
return c.JSON(http.StatusBadRequest, models.Message{"No or invalid user model provided."})
}
if datUser == nil {
return c.JSON(http.StatusBadRequest, models.Message{"No or invalid user model provided."})
}
// Insert the user
newUser, err := user.CreateUser(datUser.APIFormat())

15
vendor/github.com/asaskevich/govalidator/.gitignore generated vendored Normal file
View File

@ -0,0 +1,15 @@
bin/
.idea/
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out

View File

@ -1,14 +1,18 @@
dist: bionic
language: go
env: GO111MODULE=on GOFLAGS='-mod vendor'
install: true
email: false
go:
- 1.1
- 1.2
- 1.3
- 1.4
- 1.5
- 1.6
- 1.10
- 1.11
- 1.12
- 1.13
- tip
notifications:
email:
- bwatas@gmail.com
before_script:
- go install github.com/golangci/golangci-lint/cmd/golangci-lint
script:
- golangci-lint run # run a bunch of code checkers/linters in parallel
- go test -v -race ./... # Run all the tests with the race detector enabled

View File

@ -11,7 +11,7 @@ If you don't know what to do, there are some features and functions that need to
- [ ] Update actual [list of functions](https://github.com/asaskevich/govalidator#list-of-functions)
- [ ] Update [list of validators](https://github.com/asaskevich/govalidator#validatestruct-2) that available for `ValidateStruct` and add new
- [ ] Implement new validators: `IsFQDN`, `IsIMEI`, `IsPostalCode`, `IsISIN`, `IsISRC` etc
- [ ] Implement [validation by maps](https://github.com/asaskevich/govalidator/issues/224)
- [x] Implement [validation by maps](https://github.com/asaskevich/govalidator/issues/224)
- [ ] Implement fuzzing testing
- [ ] Implement some struct/map/array utilities
- [ ] Implement map/array validation
@ -37,7 +37,7 @@ Anyone can file an expense. If the expense makes sense for the development of th
### Contributors
Thank you to all the people who have already contributed to govalidator!
<a href="graphs/contributors"><img src="https://opencollective.com/govalidator/contributors.svg?width=890" /></a>
<a href="https://github.com/asaskevich/govalidator/graphs/contributors"><img src="https://opencollective.com/govalidator/contributors.svg?width=890" /></a>
### Backers

View File

@ -13,7 +13,7 @@ Type the following command in your terminal:
or you can get specified release of the package with `gopkg.in`:
go get gopkg.in/asaskevich/govalidator.v4
go get gopkg.in/asaskevich/govalidator.v10
After it the package is ready to use.
@ -83,14 +83,14 @@ This was changed to prevent data races when accessing custom validators.
import "github.com/asaskevich/govalidator"
// before
govalidator.CustomTypeTagMap["customByteArrayValidator"] = CustomTypeValidator(func(i interface{}, o interface{}) bool {
govalidator.CustomTypeTagMap["customByteArrayValidator"] = func(i interface{}, o interface{}) bool {
// ...
})
}
// after
govalidator.CustomTypeTagMap.Set("customByteArrayValidator", CustomTypeValidator(func(i interface{}, o interface{}) bool {
govalidator.CustomTypeTagMap.Set("customByteArrayValidator", func(i interface{}, o interface{}) bool {
// ...
}))
})
```
#### List of functions:
@ -108,23 +108,34 @@ func Filter(array []interface{}, iterator ConditionIterator) []interface{}
func Find(array []interface{}, iterator ConditionIterator) interface{}
func GetLine(s string, index int) (string, error)
func GetLines(s string) []string
func InRange(value, left, right float64) bool
func HasLowerCase(str string) bool
func HasUpperCase(str string) bool
func HasWhitespace(str string) bool
func HasWhitespaceOnly(str string) bool
func InRange(value interface{}, left interface{}, right interface{}) bool
func InRangeFloat32(value, left, right float32) bool
func InRangeFloat64(value, left, right float64) bool
func InRangeInt(value, left, right interface{}) bool
func IsASCII(str string) bool
func IsAlpha(str string) bool
func IsAlphanumeric(str string) bool
func IsBase64(str string) bool
func IsByteLength(str string, min, max int) bool
func IsCIDR(str string) bool
func IsCRC32(str string) bool
func IsCRC32b(str string) bool
func IsCreditCard(str string) bool
func IsDNSName(str string) bool
func IsDataURI(str string) bool
func IsDialString(str string) bool
func IsDivisibleBy(str, num string) bool
func IsEmail(str string) bool
func IsExistingEmail(email string) bool
func IsFilePath(str string) (bool, int)
func IsFloat(str string) bool
func IsFullWidth(str string) bool
func IsHalfWidth(str string) bool
func IsHash(str string, algorithm string) bool
func IsHexadecimal(str string) bool
func IsHexcolor(str string) bool
func IsHost(str string) bool
@ -136,22 +147,27 @@ func IsISBN10(str string) bool
func IsISBN13(str string) bool
func IsISO3166Alpha2(str string) bool
func IsISO3166Alpha3(str string) bool
func IsISO4217(str string) bool
func IsISO693Alpha2(str string) bool
func IsISO693Alpha3b(str string) bool
func IsISO4217(str string) bool
func IsIn(str string, params ...string) bool
func IsInRaw(str string, params ...string) bool
func IsInt(str string) bool
func IsJSON(str string) bool
func IsLatitude(str string) bool
func IsLongitude(str string) bool
func IsLowerCase(str string) bool
func IsMAC(str string) bool
func IsMD4(str string) bool
func IsMD5(str string) bool
func IsMagnetURI(str string) bool
func IsMongoID(str string) bool
func IsMultibyte(str string) bool
func IsNatural(value float64) bool
func IsNegative(value float64) bool
func IsNonNegative(value float64) bool
func IsNonPositive(value float64) bool
func IsNotNull(str string) bool
func IsNull(str string) bool
func IsNumeric(str string) bool
func IsPort(str string) bool
@ -162,9 +178,21 @@ func IsRFC3339WithoutZone(str string) bool
func IsRGBcolor(str string) bool
func IsRequestURI(rawurl string) bool
func IsRequestURL(rawurl string) bool
func IsRipeMD128(str string) bool
func IsRipeMD160(str string) bool
func IsRsaPub(str string, params ...string) bool
func IsRsaPublicKey(str string, keylen int) bool
func IsSHA1(str string) bool
func IsSHA256(str string) bool
func IsSHA384(str string) bool
func IsSHA512(str string) bool
func IsSSN(str string) bool
func IsSemver(str string) bool
func IsTiger128(str string) bool
func IsTiger160(str string) bool
func IsTiger192(str string) bool
func IsTime(str string, format string) bool
func IsType(v interface{}, params ...string) bool
func IsURL(str string) bool
func IsUTFDigit(str string) bool
func IsUTFLetter(str string) bool
@ -174,16 +202,20 @@ func IsUUID(str string) bool
func IsUUIDv3(str string) bool
func IsUUIDv4(str string) bool
func IsUUIDv5(str string) bool
func IsUnixTime(str string) bool
func IsUpperCase(str string) bool
func IsVariableWidth(str string) bool
func IsWhole(value float64) bool
func LeftTrim(str, chars string) string
func Map(array []interface{}, iterator ResultIterator) []interface{}
func Matches(str, pattern string) bool
func MaxStringLength(str string, params ...string) bool
func MinStringLength(str string, params ...string) bool
func NormalizeEmail(str string) (string, error)
func PadBoth(str string, padStr string, padLen int) string
func PadLeft(str string, padStr string, padLen int) string
func PadRight(str string, padStr string, padLen int) string
func PrependPathToErrors(err error, path string) error
func Range(str string, params ...string) bool
func RemoveTags(s string) string
func ReplacePattern(str, pattern, replace string) string
@ -192,18 +224,21 @@ func RightTrim(str, chars string) string
func RuneLength(str string, params ...string) bool
func SafeFileName(str string) string
func SetFieldsRequiredByDefault(value bool)
func SetNilPtrAllowedByRequired(value bool)
func Sign(value float64) float64
func StringLength(str string, params ...string) bool
func StringMatches(s string, params ...string) bool
func StripLow(str string, keepNewLines bool) string
func ToBoolean(str string) (bool, error)
func ToFloat(str string) (float64, error)
func ToInt(str string) (int64, error)
func ToInt(value interface{}) (res int64, err error)
func ToJSON(obj interface{}) (string, error)
func ToString(obj interface{}) string
func Trim(str, chars string) string
func Truncate(str string, length int, ending string) string
func TruncatingErrorf(str string, args ...interface{}) error
func UnderscoreToCamelCase(s string) string
func ValidateMap(inputMap map[string]interface{}, validationMap map[string]interface{}) (bool, error)
func ValidateStruct(s interface{}) (bool, error)
func WhiteList(str, chars string) string
type ConditionIterator
@ -214,6 +249,8 @@ type Errors
func (es Errors) Error() string
func (es Errors) Errors() []error
type ISO3166Entry
type ISO693Entry
type InterfaceParamValidator
type Iterator
type ParamValidator
type ResultIterator
@ -227,6 +264,27 @@ type Validator
```go
println(govalidator.IsURL(`http://user@pass:domain.com/path/page`))
```
###### IsType
```go
println(govalidator.IsType("Bob", "string"))
println(govalidator.IsType(1, "int"))
i := 1
println(govalidator.IsType(&i, "*int"))
```
IsType can be used through the tag `type` which is essential for map validation:
```go
type User struct {
Name string `valid:"type(string)"`
Age int `valid:"type(int)"`
Meta interface{} `valid:"type(string)"`
}
result, err := govalidator.ValidateStruct(user{"Bob", 20, "meta"})
if err != nil {
println("error: " + err.Error())
}
println(result)
```
###### ToString
```go
type User struct {
@ -335,6 +393,11 @@ Validators with parameters
"in(string1|string2|...|stringN)": IsIn,
"rsapub(keylength)" : IsRsaPub,
```
Validators with parameters for any type
```go
"type(type)": IsType,
```
And here is small example of usage:
```go
@ -370,6 +433,41 @@ if err != nil {
}
println(result)
```
###### ValidateMap [#2](https://github.com/asaskevich/govalidator/pull/338)
If you want to validate maps, you can use the map to be validated and a validation map that contain the same tags used in ValidateStruct, both maps have to be in the form `map[string]interface{}`
So here is small example of usage:
```go
var mapTemplate = map[string]interface{}{
"name":"required,alpha",
"family":"required,alpha",
"email":"required,email",
"cell-phone":"numeric",
"address":map[string]interface{}{
"line1":"required,alphanum",
"line2":"alphanum",
"postal-code":"numeric",
},
}
var inputMap = map[string]interface{}{
"name":"Bob",
"family":"Smith",
"email":"foo@bar.baz",
"address":map[string]interface{}{
"line1":"",
"line2":"",
"postal-code":"",
},
}
result, err := govalidator.ValidateMap(inputMap, mapTemplate)
if err != nil {
println("error: " + err.Error())
}
println(result)
```
###### WhiteList
```go
// Remove all characters from string ignoring characters between "a" and "z"
@ -389,7 +487,7 @@ type StructWithCustomByteArray struct {
CustomMinLength int `valid:"-"`
}
govalidator.CustomTypeTagMap.Set("customByteArrayValidator", CustomTypeValidator(func(i interface{}, context interface{}) bool {
govalidator.CustomTypeTagMap.Set("customByteArrayValidator", func(i interface{}, context interface{}) bool {
switch v := context.(type) { // you can type switch on the context interface being validated
case StructWithCustomByteArray:
// you can check and validate against some other field in the context,
@ -409,14 +507,25 @@ govalidator.CustomTypeTagMap.Set("customByteArrayValidator", CustomTypeValidator
}
}
return false
}))
govalidator.CustomTypeTagMap.Set("customMinLengthValidator", CustomTypeValidator(func(i interface{}, context interface{}) bool {
})
govalidator.CustomTypeTagMap.Set("customMinLengthValidator", func(i interface{}, context interface{}) bool {
switch v := context.(type) { // this validates a field against the value in another field, i.e. dependent validation
case StructWithCustomByteArray:
return len(v.ID) >= v.CustomMinLength
}
return false
}))
})
```
###### Loop over Error()
By default .Error() returns all errors in a single String. To access each error you can do this:
```go
if err != nil {
errs := err.(govalidator.Errors).Errors()
for _, e := range errs {
fmt.Println(e.Error())
}
}
```
###### Custom error messages
@ -445,7 +554,7 @@ If you don't know what to do, there are some features and functions that need to
- [ ] Update actual [list of functions](https://github.com/asaskevich/govalidator#list-of-functions)
- [ ] Update [list of validators](https://github.com/asaskevich/govalidator#validatestruct-2) that available for `ValidateStruct` and add new
- [ ] Implement new validators: `IsFQDN`, `IsIMEI`, `IsPostalCode`, `IsISIN`, `IsISRC` etc
- [ ] Implement [validation by maps](https://github.com/asaskevich/govalidator/issues/224)
- [x] Implement [validation by maps](https://github.com/asaskevich/govalidator/issues/224)
- [ ] Implement fuzzing testing
- [ ] Implement some struct/map/array utilities
- [ ] Implement map/array validation
@ -475,7 +584,7 @@ This project exists thanks to all the people who contribute. [[Contribute](CONTR
* [Matt Sanford](https://github.com/mzsanford)
* [Simon ccl1115](https://github.com/ccl1115)
<a href="graphs/contributors"><img src="https://opencollective.com/govalidator/contributors.svg?width=890" /></a>
<a href="https://github.com/asaskevich/govalidator/graphs/contributors"><img src="https://opencollective.com/govalidator/contributors.svg?width=890" /></a>
### Backers
@ -504,4 +613,4 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
## License
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fasaskevich%2Fgovalidator.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fasaskevich%2Fgovalidator?ref=badge_large)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fasaskevich%2Fgovalidator.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fasaskevich%2Fgovalidator?ref=badge_large)

3
vendor/github.com/asaskevich/govalidator/doc.go generated vendored Normal file
View File

@ -0,0 +1,3 @@
package govalidator
// A package of validators and sanitizers for strings, structures and collections.

View File

@ -1,6 +1,9 @@
package govalidator
import "strings"
import (
"sort"
"strings"
)
// Errors is an array of multiple errors and conforms to the error interface.
type Errors []error
@ -15,6 +18,7 @@ func (es Errors) Error() string {
for _, e := range es {
errs = append(errs, e.Error())
}
sort.Strings(errs)
return strings.Join(errs, ";")
}

3
vendor/github.com/asaskevich/govalidator/go.mod generated vendored Normal file
View File

@ -0,0 +1,3 @@
module github.com/asaskevich/govalidator
go 1.12

View File

@ -4,49 +4,51 @@ import "regexp"
// Basic regular expressions for validating strings
const (
Email string = "^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
CreditCard string = "^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$"
ISBN10 string = "^(?:[0-9]{9}X|[0-9]{10})$"
ISBN13 string = "^(?:[0-9]{13})$"
UUID3 string = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$"
UUID4 string = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
UUID5 string = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
UUID string = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
Alpha string = "^[a-zA-Z]+$"
Alphanumeric string = "^[a-zA-Z0-9]+$"
Numeric string = "^[0-9]+$"
Int string = "^(?:[-+]?(?:0|[1-9][0-9]*))$"
Float string = "^(?:[-+]?(?:[0-9]+))?(?:\\.[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$"
Hexadecimal string = "^[0-9a-fA-F]+$"
Hexcolor string = "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
RGBcolor string = "^rgb\\(\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*\\)$"
ASCII string = "^[\x00-\x7F]+$"
Multibyte string = "[^\x00-\x7F]"
FullWidth string = "[^\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]"
HalfWidth string = "[\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]"
Base64 string = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
PrintableASCII string = "^[\x20-\x7E]+$"
DataURI string = "^data:.+\\/(.+);base64$"
Latitude string = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"
Longitude string = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
DNSName string = `^([a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*[\._]?$`
IP string = `(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))`
URLSchema string = `((ftp|tcp|udp|wss?|https?):\/\/)`
URLUsername string = `(\S+(:\S*)?@)`
URLPath string = `((\/|\?|#)[^\s]*)`
URLPort string = `(:(\d{1,5}))`
URLIP string = `([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))`
URLSubdomain string = `((www\.)|([a-zA-Z0-9]+([-_\.]?[a-zA-Z0-9])*[a-zA-Z0-9]\.[a-zA-Z0-9]+))`
URL string = `^` + URLSchema + `?` + URLUsername + `?` + `((` + URLIP + `|(\[` + IP + `\])|(([a-zA-Z0-9]([a-zA-Z0-9-_]+)?[a-zA-Z0-9]([-\.][a-zA-Z0-9]+)*)|(` + URLSubdomain + `?))?(([a-zA-Z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-zA-Z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-zA-Z\x{00a1}-\x{ffff}]{1,}))?))\.?` + URLPort + `?` + URLPath + `?$`
SSN string = `^\d{3}[- ]?\d{2}[- ]?\d{4}$`
WinPath string = `^[a-zA-Z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$`
UnixPath string = `^(/[^/\x00]*)+/?$`
Semver string = "^v?(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(-(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\\+[0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)?$"
tagName string = "valid"
hasLowerCase string = ".*[[:lower:]]"
hasUpperCase string = ".*[[:upper:]]"
hasWhitespace string = ".*[[:space:]]"
hasWhitespaceOnly string = "^[[:space:]]+$"
Email string = "^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
CreditCard string = "^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11}|6[27][0-9]{14})$"
ISBN10 string = "^(?:[0-9]{9}X|[0-9]{10})$"
ISBN13 string = "^(?:[0-9]{13})$"
UUID3 string = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$"
UUID4 string = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
UUID5 string = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
UUID string = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
Alpha string = "^[a-zA-Z]+$"
Alphanumeric string = "^[a-zA-Z0-9]+$"
Numeric string = "^[0-9]+$"
Int string = "^(?:[-+]?(?:0|[1-9][0-9]*))$"
Float string = "^(?:[-+]?(?:[0-9]+))?(?:\\.[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$"
Hexadecimal string = "^[0-9a-fA-F]+$"
Hexcolor string = "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
RGBcolor string = "^rgb\\(\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*\\)$"
ASCII string = "^[\x00-\x7F]+$"
Multibyte string = "[^\x00-\x7F]"
FullWidth string = "[^\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]"
HalfWidth string = "[\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]"
Base64 string = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
PrintableASCII string = "^[\x20-\x7E]+$"
DataURI string = "^data:.+\\/(.+);base64$"
MagnetURI string = "^magnet:\\?xt=urn:[a-zA-Z0-9]+:[a-zA-Z0-9]{32,40}&dn=.+&tr=.+$"
Latitude string = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"
Longitude string = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
DNSName string = `^([a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*[\._]?$`
IP string = `(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))`
URLSchema string = `((ftp|tcp|udp|wss?|https?):\/\/)`
URLUsername string = `(\S+(:\S*)?@)`
URLPath string = `((\/|\?|#)[^\s]*)`
URLPort string = `(:(\d{1,5}))`
URLIP string = `([1-9]\d?|1\d\d|2[01]\d|22[0-3]|24\d|25[0-5])(\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-5]))`
URLSubdomain string = `((www\.)|([a-zA-Z0-9]+([-_\.]?[a-zA-Z0-9])*[a-zA-Z0-9]\.[a-zA-Z0-9]+))`
URL string = `^` + URLSchema + `?` + URLUsername + `?` + `((` + URLIP + `|(\[` + IP + `\])|(([a-zA-Z0-9]([a-zA-Z0-9-_]+)?[a-zA-Z0-9]([-\.][a-zA-Z0-9]+)*)|(` + URLSubdomain + `?))?(([a-zA-Z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-zA-Z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-zA-Z\x{00a1}-\x{ffff}]{1,}))?))\.?` + URLPort + `?` + URLPath + `?$`
SSN string = `^\d{3}[- ]?\d{2}[- ]?\d{4}$`
WinPath string = `^[a-zA-Z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$`
UnixPath string = `^(/[^/\x00]*)+/?$`
Semver string = "^v?(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(-(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\\+[0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)?$"
tagName string = "valid"
hasLowerCase string = ".*[[:lower:]]"
hasUpperCase string = ".*[[:upper:]]"
hasWhitespace string = ".*[[:space:]]"
hasWhitespaceOnly string = "^[[:space:]]+$"
IMEI string = "^[0-9a-f]{14}$|^\\d{15}$|^\\d{18}$"
)
// Used by IsFilePath func
@ -60,42 +62,44 @@ const (
)
var (
userRegexp = regexp.MustCompile("^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+$")
hostRegexp = regexp.MustCompile("^[^\\s]+\\.[^\\s]+$")
userDotRegexp = regexp.MustCompile("(^[.]{1})|([.]{1}$)|([.]{2,})")
rxEmail = regexp.MustCompile(Email)
rxCreditCard = regexp.MustCompile(CreditCard)
rxISBN10 = regexp.MustCompile(ISBN10)
rxISBN13 = regexp.MustCompile(ISBN13)
rxUUID3 = regexp.MustCompile(UUID3)
rxUUID4 = regexp.MustCompile(UUID4)
rxUUID5 = regexp.MustCompile(UUID5)
rxUUID = regexp.MustCompile(UUID)
rxAlpha = regexp.MustCompile(Alpha)
rxAlphanumeric = regexp.MustCompile(Alphanumeric)
rxNumeric = regexp.MustCompile(Numeric)
rxInt = regexp.MustCompile(Int)
rxFloat = regexp.MustCompile(Float)
rxHexadecimal = regexp.MustCompile(Hexadecimal)
rxHexcolor = regexp.MustCompile(Hexcolor)
rxRGBcolor = regexp.MustCompile(RGBcolor)
rxASCII = regexp.MustCompile(ASCII)
rxPrintableASCII = regexp.MustCompile(PrintableASCII)
rxMultibyte = regexp.MustCompile(Multibyte)
rxFullWidth = regexp.MustCompile(FullWidth)
rxHalfWidth = regexp.MustCompile(HalfWidth)
rxBase64 = regexp.MustCompile(Base64)
rxDataURI = regexp.MustCompile(DataURI)
rxLatitude = regexp.MustCompile(Latitude)
rxLongitude = regexp.MustCompile(Longitude)
rxDNSName = regexp.MustCompile(DNSName)
rxURL = regexp.MustCompile(URL)
rxSSN = regexp.MustCompile(SSN)
rxWinPath = regexp.MustCompile(WinPath)
rxUnixPath = regexp.MustCompile(UnixPath)
rxSemver = regexp.MustCompile(Semver)
rxHasLowerCase = regexp.MustCompile(hasLowerCase)
rxHasUpperCase = regexp.MustCompile(hasUpperCase)
rxHasWhitespace = regexp.MustCompile(hasWhitespace)
rxHasWhitespaceOnly = regexp.MustCompile(hasWhitespaceOnly)
userRegexp = regexp.MustCompile("^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+$")
hostRegexp = regexp.MustCompile("^[^\\s]+\\.[^\\s]+$")
userDotRegexp = regexp.MustCompile("(^[.]{1})|([.]{1}$)|([.]{2,})")
rxEmail = regexp.MustCompile(Email)
rxCreditCard = regexp.MustCompile(CreditCard)
rxISBN10 = regexp.MustCompile(ISBN10)
rxISBN13 = regexp.MustCompile(ISBN13)
rxUUID3 = regexp.MustCompile(UUID3)
rxUUID4 = regexp.MustCompile(UUID4)
rxUUID5 = regexp.MustCompile(UUID5)
rxUUID = regexp.MustCompile(UUID)
rxAlpha = regexp.MustCompile(Alpha)
rxAlphanumeric = regexp.MustCompile(Alphanumeric)
rxNumeric = regexp.MustCompile(Numeric)
rxInt = regexp.MustCompile(Int)
rxFloat = regexp.MustCompile(Float)
rxHexadecimal = regexp.MustCompile(Hexadecimal)
rxHexcolor = regexp.MustCompile(Hexcolor)
rxRGBcolor = regexp.MustCompile(RGBcolor)
rxASCII = regexp.MustCompile(ASCII)
rxPrintableASCII = regexp.MustCompile(PrintableASCII)
rxMultibyte = regexp.MustCompile(Multibyte)
rxFullWidth = regexp.MustCompile(FullWidth)
rxHalfWidth = regexp.MustCompile(HalfWidth)
rxBase64 = regexp.MustCompile(Base64)
rxDataURI = regexp.MustCompile(DataURI)
rxMagnetURI = regexp.MustCompile(MagnetURI)
rxLatitude = regexp.MustCompile(Latitude)
rxLongitude = regexp.MustCompile(Longitude)
rxDNSName = regexp.MustCompile(DNSName)
rxURL = regexp.MustCompile(URL)
rxSSN = regexp.MustCompile(SSN)
rxWinPath = regexp.MustCompile(WinPath)
rxUnixPath = regexp.MustCompile(UnixPath)
rxSemver = regexp.MustCompile(Semver)
rxHasLowerCase = regexp.MustCompile(hasLowerCase)
rxHasUpperCase = regexp.MustCompile(hasUpperCase)
rxHasWhitespace = regexp.MustCompile(hasWhitespace)
rxHasWhitespaceOnly = regexp.MustCompile(hasWhitespaceOnly)
rxIMEI = regexp.MustCompile(IMEI)
)

View File

@ -16,6 +16,7 @@ type CustomTypeValidator func(i interface{}, o interface{}) bool
// ParamValidator is a wrapper for validator functions that accepts additional parameters.
type ParamValidator func(str string, params ...string) bool
type InterfaceParamValidator func(in interface{}, params ...string) bool
type tagOptionsMap map[string]tagOption
func (t tagOptionsMap) orderedKeys() []string {
@ -46,15 +47,27 @@ type UnsupportedTypeError struct {
// It implements the methods to sort by string.
type stringValues []reflect.Value
// InterfaceParamTagMap is a map of functions accept variants parameters for an interface value
var InterfaceParamTagMap = map[string]InterfaceParamValidator{
"type": IsType,
}
// InterfaceParamTagRegexMap maps interface param tags to their respective regexes.
var InterfaceParamTagRegexMap = map[string]*regexp.Regexp{
"type": regexp.MustCompile(`^type\((.*)\)$`),
}
// ParamTagMap is a map of functions accept variants parameters
var ParamTagMap = map[string]ParamValidator{
"length": ByteLength,
"range": Range,
"runelength": RuneLength,
"stringlength": StringLength,
"matches": StringMatches,
"in": isInRaw,
"rsapub": IsRsaPub,
"length": ByteLength,
"range": Range,
"runelength": RuneLength,
"stringlength": StringLength,
"matches": StringMatches,
"in": IsInRaw,
"rsapub": IsRsaPub,
"minstringlength": MinStringLength,
"maxstringlength": MaxStringLength,
}
// ParamTagRegexMap maps param tags to their respective regexes.
@ -66,6 +79,8 @@ var ParamTagRegexMap = map[string]*regexp.Regexp{
"in": regexp.MustCompile(`^in\((.*)\)`),
"matches": regexp.MustCompile(`^matches\((.+)\)$`),
"rsapub": regexp.MustCompile("^rsapub\\((\\d+)\\)$"),
"minstringlength": regexp.MustCompile("^minstringlength\\((\\d+)\\)$"),
"maxstringlength": regexp.MustCompile("^maxstringlength\\((\\d+)\\)$"),
}
type customTypeTagMap struct {
@ -114,6 +129,7 @@ var TagMap = map[string]Validator{
"int": IsInt,
"float": IsFloat,
"null": IsNull,
"notnull": IsNotNull,
"uuid": IsUUID,
"uuidv3": IsUUIDv3,
"uuidv4": IsUUIDv4,
@ -146,6 +162,7 @@ var TagMap = map[string]Validator{
"ISO3166Alpha2": IsISO3166Alpha2,
"ISO3166Alpha3": IsISO3166Alpha3,
"ISO4217": IsISO4217,
"IMEI": IsIMEI,
}
// ISO3166Entry stores country codes

View File

@ -12,20 +12,20 @@ import (
"unicode/utf8"
)
// Contains check if the string contains the substring.
// Contains checks if the string contains the substring.
func Contains(str, substring string) bool {
return strings.Contains(str, substring)
}
// Matches check if string matches the pattern (pattern is regular expression)
// Matches checks if string matches the pattern (pattern is regular expression)
// In case of error return false
func Matches(str, pattern string) bool {
match, _ := regexp.MatchString(pattern, str)
return match
}
// LeftTrim trim characters from the left-side of the input.
// If second argument is empty, it's will be remove leading spaces.
// LeftTrim trims characters from the left side of the input.
// If second argument is empty, it will remove leading spaces.
func LeftTrim(str, chars string) string {
if chars == "" {
return strings.TrimLeftFunc(str, unicode.IsSpace)
@ -34,8 +34,8 @@ func LeftTrim(str, chars string) string {
return r.ReplaceAllString(str, "")
}
// RightTrim trim characters from the right-side of the input.
// If second argument is empty, it's will be remove spaces.
// RightTrim trims characters from the right side of the input.
// If second argument is empty, it will remove trailing spaces.
func RightTrim(str, chars string) string {
if chars == "" {
return strings.TrimRightFunc(str, unicode.IsSpace)
@ -44,27 +44,27 @@ func RightTrim(str, chars string) string {
return r.ReplaceAllString(str, "")
}
// Trim trim characters from both sides of the input.
// If second argument is empty, it's will be remove spaces.
// Trim trims characters from both sides of the input.
// If second argument is empty, it will remove spaces.
func Trim(str, chars string) string {
return LeftTrim(RightTrim(str, chars), chars)
}
// WhiteList remove characters that do not appear in the whitelist.
// WhiteList removes characters that do not appear in the whitelist.
func WhiteList(str, chars string) string {
pattern := "[^" + chars + "]+"
r, _ := regexp.Compile(pattern)
return r.ReplaceAllString(str, "")
}
// BlackList remove characters that appear in the blacklist.
// BlackList removes characters that appear in the blacklist.
func BlackList(str, chars string) string {
pattern := "[" + chars + "]+"
r, _ := regexp.Compile(pattern)
return r.ReplaceAllString(str, "")
}
// StripLow remove characters with a numerical value < 32 and 127, mostly control characters.
// StripLow removes characters with a numerical value < 32 and 127, mostly control characters.
// If keep_new_lines is true, newline characters are preserved (\n and \r, hex 0xA and 0xD).
func StripLow(str string, keepNewLines bool) string {
chars := ""
@ -76,13 +76,13 @@ func StripLow(str string, keepNewLines bool) string {
return BlackList(str, chars)
}
// ReplacePattern replace regular expression pattern in string
// ReplacePattern replaces regular expression pattern in string
func ReplacePattern(str, pattern, replace string) string {
r, _ := regexp.Compile(pattern)
return r.ReplaceAllString(str, replace)
}
// Escape replace <, >, & and " with HTML entities.
// Escape replaces <, >, & and " with HTML entities.
var Escape = html.EscapeString
func addSegment(inrune, segment []rune) []rune {
@ -120,7 +120,7 @@ func CamelCaseToUnderscore(str string) string {
return string(output)
}
// Reverse return reversed string
// Reverse returns reversed string
func Reverse(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
@ -129,12 +129,12 @@ func Reverse(s string) string {
return string(r)
}
// GetLines split string by "\n" and return array of lines
// GetLines splits string by "\n" and return array of lines
func GetLines(s string) []string {
return strings.Split(s, "\n")
}
// GetLine return specified line of multiline string
// GetLine returns specified line of multiline string
func GetLine(s string, index int) (string, error) {
lines := GetLines(s)
if index < 0 || index >= len(lines) {
@ -143,12 +143,12 @@ func GetLine(s string, index int) (string, error) {
return lines[index], nil
}
// RemoveTags remove all tags from HTML string
// RemoveTags removes all tags from HTML string
func RemoveTags(s string) string {
return ReplacePattern(s, "<[^>]*>", "")
}
// SafeFileName return safe string that can be used in file names
// SafeFileName returns safe string that can be used in file names
func SafeFileName(str string) string {
name := strings.ToLower(str)
name = path.Clean(path.Base(name))
@ -210,23 +210,23 @@ func Truncate(str string, length int, ending string) string {
return str
}
// PadLeft pad left side of string if size of string is less then indicated pad length
// PadLeft pads left side of a string if size of string is less then indicated pad length
func PadLeft(str string, padStr string, padLen int) string {
return buildPadStr(str, padStr, padLen, true, false)
}
// PadRight pad right side of string if size of string is less then indicated pad length
// PadRight pads right side of a string if size of string is less then indicated pad length
func PadRight(str string, padStr string, padLen int) string {
return buildPadStr(str, padStr, padLen, false, true)
}
// PadBoth pad sides of string if size of string is less then indicated pad length
// PadBoth pads both sides of a string if size of string is less then indicated pad length
func PadBoth(str string, padStr string, padLen int) string {
return buildPadStr(str, padStr, padLen, true, true)
}
// PadString either left, right or both sides, not the padding string can be unicode and more then one
// character
// PadString either left, right or both sides.
// Note that padding string can be unicode and more then one character
func buildPadStr(str string, padStr string, padLen int, padLeft bool, padRight bool) string {
// When padded length is less then the current string size

View File

@ -282,7 +282,7 @@ func HasLowerCase(str string) bool {
return rxHasLowerCase.MatchString(str)
}
// HasUpperCase check if the string contians as least 1 uppercase. Empty string is valid.
// HasUpperCase check if the string contains as least 1 uppercase. Empty string is valid.
func HasUpperCase(str string) bool {
if IsNull(str) {
return true
@ -321,14 +321,19 @@ func IsNull(str string) bool {
return len(str) == 0
}
// IsNotNull check if the string is not null.
func IsNotNull(str string) bool {
return !IsNull(str)
}
// HasWhitespaceOnly checks the string only contains whitespace
func HasWhitespaceOnly(str string) bool {
return len(str) > 0 && rxHasWhitespaceOnly.MatchString(str)
return len(str) > 0 && rxHasWhitespaceOnly.MatchString(str)
}
// HasWhitespace checks if the string contains any whitespace
func HasWhitespace(str string) bool {
return len(str) > 0 && rxHasWhitespace.MatchString(str)
return len(str) > 0 && rxHasWhitespace.MatchString(str)
}
// IsByteLength check if the string's length (in bytes) falls in a range.
@ -513,6 +518,11 @@ func IsDataURI(str string) bool {
return IsBase64(dataURI[1])
}
// IsMagnetURI checks if a string is valid magnet URI
func IsMagnetURI(str string) bool {
return rxMagnetURI.MatchString(str)
}
// IsISO3166Alpha2 checks if a string is valid two-letter country code
func IsISO3166Alpha2(str string) bool {
for _, entry := range ISO3166List {
@ -565,7 +575,7 @@ func IsDNSName(str string) bool {
// IsHash checks if a string is a hash of type algorithm.
// Algorithm is one of ['md4', 'md5', 'sha1', 'sha256', 'sha384', 'sha512', 'ripemd128', 'ripemd160', 'tiger128', 'tiger160', 'tiger192', 'crc32', 'crc32b']
func IsHash(str string, algorithm string) bool {
len := "0"
var len string
algo := strings.ToLower(algorithm)
if algo == "crc32" || algo == "crc32b" {
@ -589,9 +599,73 @@ func IsHash(str string, algorithm string) bool {
return Matches(str, "^[a-f0-9]{"+len+"}$")
}
// IsSHA512 checks is a string is a SHA512 hash. Alias for `IsHash(str, "sha512")`
func IsSHA512(str string) bool {
return IsHash(str, "sha512")
}
// IsSHA384 checks is a string is a SHA384 hash. Alias for `IsHash(str, "sha384")`
func IsSHA384(str string) bool {
return IsHash(str, "sha384")
}
// IsSHA256 checks is a string is a SHA256 hash. Alias for `IsHash(str, "sha256")`
func IsSHA256(str string) bool {
return IsHash(str, "sha256")
}
// IsTiger192 checks is a string is a Tiger192 hash. Alias for `IsHash(str, "tiger192")`
func IsTiger192(str string) bool {
return IsHash(str, "tiger192")
}
// IsTiger160 checks is a string is a Tiger160 hash. Alias for `IsHash(str, "tiger160")`
func IsTiger160(str string) bool {
return IsHash(str, "tiger160")
}
// IsRipeMD160 checks is a string is a RipeMD160 hash. Alias for `IsHash(str, "ripemd160")`
func IsRipeMD160(str string) bool {
return IsHash(str, "ripemd160")
}
// IsSHA1 checks is a string is a SHA-1 hash. Alias for `IsHash(str, "sha1")`
func IsSHA1(str string) bool {
return IsHash(str, "sha1")
}
// IsTiger128 checks is a string is a Tiger128 hash. Alias for `IsHash(str, "tiger128")`
func IsTiger128(str string) bool {
return IsHash(str, "tiger128")
}
// IsRipeMD128 checks is a string is a RipeMD128 hash. Alias for `IsHash(str, "ripemd128")`
func IsRipeMD128(str string) bool {
return IsHash(str, "ripemd128")
}
// IsCRC32 checks is a string is a CRC32 hash. Alias for `IsHash(str, "crc32")`
func IsCRC32(str string) bool {
return IsHash(str, "crc32")
}
// IsCRC32b checks is a string is a CRC32b hash. Alias for `IsHash(str, "crc32b")`
func IsCRC32b(str string) bool {
return IsHash(str, "crc32b")
}
// IsMD5 checks is a string is a MD5 hash. Alias for `IsHash(str, "md5")`
func IsMD5(str string) bool {
return IsHash(str, "md5")
}
// IsMD4 checks is a string is a MD4 hash. Alias for `IsHash(str, "md4")`
func IsMD4(str string) bool {
return IsHash(str, "md4")
}
// IsDialString validates the given string for usage with the various Dial() functions
func IsDialString(str string) bool {
if h, p, err := net.SplitHostPort(str); err == nil && h != "" && p != "" && (IsDNSName(h) || IsIP(h)) && IsPort(p) {
return true
}
@ -599,7 +673,7 @@ func IsDialString(str string) bool {
return false
}
// IsIP checks if a string is either IP version 4 or 6.
// IsIP checks if a string is either IP version 4 or 6. Alias for `net.ParseIP`
func IsIP(str string) bool {
return net.ParseIP(str) != nil
}
@ -663,6 +737,11 @@ func IsLongitude(str string) bool {
return rxLongitude.MatchString(str)
}
// IsIMEI check if a string is valid IMEI
func IsIMEI(str string) bool {
return rxIMEI.MatchString(str)
}
// IsRsaPublicKey check if a string is valid public key with provided length
func IsRsaPublicKey(str string, keylen int) bool {
bb := bytes.NewBufferString(str)
@ -729,12 +808,116 @@ func PrependPathToErrors(err error, path string) error {
}
return err2
}
fmt.Println(err)
return err
}
// ValidateMap use validation map for fields.
// result will be equal to `false` if there are any errors.
// s is the map containing the data to be validated.
// m is the validation map in the form:
// map[string]interface{}{"name":"required,alpha","address":map[string]interface{}{"line1":"required,alphanum"}}
func ValidateMap(s map[string]interface{}, m map[string]interface{}) (bool, error) {
if s == nil {
return true, nil
}
result := true
var err error
var errs Errors
var index int
val := reflect.ValueOf(s)
for key, value := range s {
presentResult := true
validator, ok := m[key]
if !ok {
presentResult = false
var err error
err = fmt.Errorf("all map keys has to be present in the validation map; got %s", key)
err = PrependPathToErrors(err, key)
errs = append(errs, err)
}
valueField := reflect.ValueOf(value)
mapResult := true
typeResult := true
structResult := true
resultField := true
switch subValidator := validator.(type) {
case map[string]interface{}:
var err error
if v, ok := value.(map[string]interface{}); !ok {
mapResult = false
err = fmt.Errorf("map validator has to be for the map type only; got %s", valueField.Type().String())
err = PrependPathToErrors(err, key)
errs = append(errs, err)
} else {
mapResult, err = ValidateMap(v, subValidator)
if err != nil {
mapResult = false
err = PrependPathToErrors(err, key)
errs = append(errs, err)
}
}
case string:
if (valueField.Kind() == reflect.Struct ||
(valueField.Kind() == reflect.Ptr && valueField.Elem().Kind() == reflect.Struct)) &&
subValidator != "-" {
var err error
structResult, err = ValidateStruct(valueField.Interface())
if err != nil {
err = PrependPathToErrors(err, key)
errs = append(errs, err)
}
}
resultField, err = typeCheck(valueField, reflect.StructField{
Name: key,
PkgPath: "",
Type: val.Type(),
Tag: reflect.StructTag(fmt.Sprintf("%s:%q", tagName, subValidator)),
Offset: 0,
Index: []int{index},
Anonymous: false,
}, val, nil)
if err != nil {
errs = append(errs, err)
}
case nil:
// already handlerd when checked before
default:
typeResult = false
err = fmt.Errorf("map validator has to be either map[string]interface{} or string; got %s", valueField.Type().String())
err = PrependPathToErrors(err, key)
errs = append(errs, err)
}
result = result && presentResult && typeResult && resultField && structResult && mapResult
index++
}
// check required keys
requiredResult := true
for key, value := range m {
if schema, ok := value.(string); ok {
tags := parseTagIntoMap(schema)
if required, ok := tags["required"]; ok {
if _, ok := s[key]; !ok {
requiredResult = false
if required.customErrorMessage != "" {
err = Error{key, fmt.Errorf(required.customErrorMessage), true, "required", []string{}}
} else {
err = Error{key, fmt.Errorf("required field missing"), false, "required", []string{}}
}
errs = append(errs, err)
}
}
}
}
if len(errs) > 0 {
err = errs
}
return result && requiredResult, err
}
// ValidateStruct use tags for fields.
// result will be equal to `false` if there are any errors.
// todo currently there is no guarantee that errors will be returned in predictable order (tests may to fail)
func ValidateStruct(s interface{}) (bool, error) {
if s == nil {
return true, nil
@ -856,12 +1039,29 @@ func IsSemver(str string) bool {
return rxSemver.MatchString(str)
}
// IsType check if interface is of some type
func IsType(v interface{}, params ...string) bool {
if len(params) == 1 {
typ := params[0]
return strings.Replace(reflect.TypeOf(v).String(), " ", "", -1) == strings.Replace(typ, " ", "", -1)
}
return false
}
// IsTime check if string is valid according to given format
func IsTime(str string, format string) bool {
_, err := time.Parse(format, str)
return err == nil
}
// IsUnixTime check if string is valid unix timestamp value
func IsUnixTime(str string) bool {
if _, err := strconv.Atoi(str); err == nil {
return true
}
return false
}
// IsRFC3339 check if string is valid timestamp value according to RFC3339
func IsRFC3339(str string) bool {
return IsTime(str, time.RFC3339)
@ -933,6 +1133,30 @@ func StringLength(str string, params ...string) bool {
return false
}
// MinStringLength check string's minimum length (including multi byte strings)
func MinStringLength(str string, params ...string) bool {
if len(params) == 1 {
strLength := utf8.RuneCountInString(str)
min, _ := ToInt(params[0])
return strLength >= int(min)
}
return false
}
// MaxStringLength check string's maximum length (including multi byte strings)
func MaxStringLength(str string, params ...string) bool {
if len(params) == 1 {
strLength := utf8.RuneCountInString(str)
max, _ := ToInt(params[0])
return strLength <= int(max)
}
return false
}
// Range check string's length
func Range(str string, params ...string) bool {
if len(params) == 2 {
@ -945,7 +1169,7 @@ func Range(str string, params ...string) bool {
return false
}
func isInRaw(str string, params ...string) bool {
func IsInRaw(str string, params ...string) bool {
if len(params) == 1 {
rawParams := params[0]
@ -1014,7 +1238,7 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value, options
options = parseTagIntoMap(tag)
}
if isEmptyValue(v) {
if !isFieldSet(v) {
// an empty value is not validated, check only required
isValid, resultErr = checkRequired(v, t, options)
for key := range options {
@ -1062,6 +1286,45 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value, options
}()
}
for _, validatorSpec := range optionsOrder {
validatorStruct := options[validatorSpec]
var negate bool
validator := validatorSpec
customMsgExists := len(validatorStruct.customErrorMessage) > 0
// Check whether the tag looks like '!something' or 'something'
if validator[0] == '!' {
validator = validator[1:]
negate = true
}
// Check for interface param validators
for key, value := range InterfaceParamTagRegexMap {
ps := value.FindStringSubmatch(validator)
if len(ps) == 0 {
continue
}
validatefunc, ok := InterfaceParamTagMap[key]
if !ok {
continue
}
delete(options, validatorSpec)
field := fmt.Sprint(v)
if result := validatefunc(v.Interface(), ps[1:]...); (!result && !negate) || (result && negate) {
if customMsgExists {
return false, Error{t.Name, TruncatingErrorf(validatorStruct.customErrorMessage, field, validator), customMsgExists, stripParams(validatorSpec), []string{}}
}
if negate {
return false, Error{t.Name, fmt.Errorf("%s does validate as %s", field, validator), customMsgExists, stripParams(validatorSpec), []string{}}
}
return false, Error{t.Name, fmt.Errorf("%s does not validate as %s", field, validator), customMsgExists, stripParams(validatorSpec), []string{}}
}
}
}
switch v.Kind() {
case reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
@ -1121,10 +1384,10 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value, options
delete(options, validatorSpec)
switch v.Kind() {
case reflect.String,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
case reflect.String,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
field := fmt.Sprint(v) // make value into string, then validate with regex
if result := validatefunc(field); !result && !negate || result && negate {
if customMsgExists {
@ -1202,7 +1465,7 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value, options
}
return typeCheck(v.Elem(), t, o, options)
case reflect.Struct:
return ValidateStruct(v.Interface())
return true, nil
default:
return false, &UnsupportedTypeError{v.Type()}
}
@ -1212,25 +1475,14 @@ func stripParams(validatorString string) string {
return paramsRegexp.ReplaceAllString(validatorString, "")
}
func isEmptyValue(v reflect.Value) bool {
// isFieldSet returns false for nil pointers, interfaces, maps, and slices. For all other values, it returns true.
func isFieldSet(v reflect.Value) bool {
switch v.Kind() {
case reflect.String, reflect.Array:
return v.Len() == 0
case reflect.Map, reflect.Slice:
return v.Len() == 0 || v.IsNil()
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
case reflect.Map, reflect.Slice, reflect.Interface, reflect.Ptr:
return !v.IsNil()
}
return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())
return true
}
// ErrorByField returns error for specified field of the struct
@ -1252,11 +1504,11 @@ func ErrorsByField(e error) map[string]string {
}
// prototype for ValidateStruct
switch e.(type) {
switch e := e.(type) {
case Error:
m[e.(Error).Name] = e.(Error).Err.Error()
m[e.Name] = e.Err.Error()
case Errors:
for _, item := range e.(Errors).Errors() {
for _, item := range e.Errors() {
n := ErrorsByField(item)
for k, v := range n {
m[k] = v

View File

@ -12,4 +12,4 @@ build:
- script:
name: go test
code: |
go test -race ./...
go test -race -v ./...

2
vendor/modules.txt vendored
View File

@ -18,7 +18,7 @@ github.com/PuerkitoBio/urlesc
# github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
github.com/alecthomas/template
github.com/alecthomas/template/parse
# github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a
# github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535
github.com/asaskevich/govalidator
# github.com/beevik/etree v1.1.0
github.com/beevik/etree