Merge branch 'master' into uptsdb

This commit is contained in:
Fabian Reinartz 2017-05-16 16:48:37 +02:00
commit 06c2b76cd4
20 changed files with 496 additions and 114 deletions

View file

@ -72,6 +72,7 @@ func Main() int {
log.Infoln("Starting prometheus", version.Info()) log.Infoln("Starting prometheus", version.Info())
log.Infoln("Build context", version.BuildContext()) log.Infoln("Build context", version.BuildContext())
log.Infoln("Host details", Uname())
var ( var (
// sampleAppender = storage.Fanout{} // sampleAppender = storage.Fanout{}

View file

@ -0,0 +1,23 @@
// Copyright 2017 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !linux
package main
import "runtime"
// Uname for any platform other than linux.
func Uname() string {
return "(" + runtime.GOOS + ")"
}

View file

@ -0,0 +1,35 @@
// Copyright 2017 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"log"
"syscall"
)
// Uname returns the uname of the host machine.
func Uname() string {
buf := syscall.Utsname{}
err := syscall.Uname(&buf)
if err != nil {
log.Fatal("Error!")
}
str := "(" + charsToString(buf.Sysname[:])
str += " " + charsToString(buf.Release[:])
str += " " + charsToString(buf.Version[:])
str += " " + charsToString(buf.Machine[:])
str += " " + charsToString(buf.Nodename[:])
str += " " + charsToString(buf.Domainname[:]) + ")"
return str
}

View file

@ -0,0 +1,25 @@
// Copyright 2017 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build 386 amd64 arm64 mips64 mips64le mips mipsle
// +build linux
package main
func charsToString(ca []int8) string {
s := make([]byte, len(ca))
for i, c := range ca {
s[i] = byte(c)
}
return string(s[0:len(ca)])
}

View file

@ -0,0 +1,25 @@
// Copyright 2017 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build arm ppc64 ppc64le s390x
// +build linux
package main
func charsToString(ca []uint8) string {
s := make([]byte, len(ca))
for i, c := range ca {
s[i] = byte(c)
}
return string(s[0:len(ca)])
}

View file

@ -15,6 +15,7 @@ package rules
import ( import (
"fmt" "fmt"
"net/url"
"sync" "sync"
"time" "time"
@ -151,7 +152,7 @@ const resolvedRetention = 15 * time.Minute
// Eval evaluates the rule expression and then creates pending alerts and fires // Eval evaluates the rule expression and then creates pending alerts and fires
// or removes previously pending alerts accordingly. // or removes previously pending alerts accordingly.
func (r *AlertingRule) Eval(ctx context.Context, ts time.Time, engine *promql.Engine, externalURLPath string) (promql.Vector, error) { func (r *AlertingRule) Eval(ctx context.Context, ts time.Time, engine *promql.Engine, externalURL *url.URL) (promql.Vector, error) {
query, err := engine.NewInstantQuery(r.vector.String(), ts) query, err := engine.NewInstantQuery(r.vector.String(), ts)
if err != nil { if err != nil {
return nil, err return nil, err
@ -194,7 +195,7 @@ func (r *AlertingRule) Eval(ctx context.Context, ts time.Time, engine *promql.En
tmplData, tmplData,
model.Time(timestamp.FromTime(ts)), model.Time(timestamp.FromTime(ts)),
engine, engine,
externalURLPath, externalURL,
) )
result, err := tmpl.Expand() result, err := tmpl.Expand()
if err != nil { if err != nil {

View file

@ -112,7 +112,7 @@ const (
type Rule interface { type Rule interface {
Name() string Name() string
// eval evaluates the rule, including any associated recording or alerting actions. // eval evaluates the rule, including any associated recording or alerting actions.
Eval(context.Context, time.Time, *promql.Engine, string) (promql.Vector, error) Eval(context.Context, time.Time, *promql.Engine, *url.URL) (promql.Vector, error)
// String returns a human-readable string representation of the rule. // String returns a human-readable string representation of the rule.
String() string String() string
// HTMLSnippet returns a human-readable string representation of the rule, // HTMLSnippet returns a human-readable string representation of the rule,
@ -270,7 +270,7 @@ func (g *Group) Eval() {
evalTotal.WithLabelValues(rtyp).Inc() evalTotal.WithLabelValues(rtyp).Inc()
vector, err := rule.Eval(g.opts.Context, now, g.opts.QueryEngine, g.opts.ExternalURL.Path) vector, err := rule.Eval(g.opts.Context, now, g.opts.QueryEngine, g.opts.ExternalURL)
if err != nil { if err != nil {
// Canceled queries are intentional termination of queries. This normally // Canceled queries are intentional termination of queries. This normally
// happens on shutdown and thus we skip logging of any errors here. // happens on shutdown and thus we skip logging of any errors here.

View file

@ -109,7 +109,7 @@ func TestAlertingRule(t *testing.T) {
for i, test := range tests { for i, test := range tests {
evalTime := baseTime.Add(test.time) evalTime := baseTime.Add(test.time)
res, err := rule.Eval(suite.Context(), evalTime, suite.QueryEngine(), "") res, err := rule.Eval(suite.Context(), evalTime, suite.QueryEngine(), nil)
if err != nil { if err != nil {
t.Fatalf("Error during alerting rule evaluation: %s", err) t.Fatalf("Error during alerting rule evaluation: %s", err)
} }

View file

@ -16,6 +16,7 @@ package rules
import ( import (
"fmt" "fmt"
"html/template" "html/template"
"net/url"
"time" "time"
"golang.org/x/net/context" "golang.org/x/net/context"
@ -47,7 +48,7 @@ func (rule RecordingRule) Name() string {
} }
// Eval evaluates the rule and then overrides the metric names and labels accordingly. // Eval evaluates the rule and then overrides the metric names and labels accordingly.
func (rule RecordingRule) Eval(ctx context.Context, ts time.Time, engine *promql.Engine, _ string) (promql.Vector, error) { func (rule RecordingRule) Eval(ctx context.Context, ts time.Time, engine *promql.Engine, _ *url.URL) (promql.Vector, error) {
query, err := engine.NewInstantQuery(rule.vector.String(), ts) query, err := engine.NewInstantQuery(rule.vector.String(), ts)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -64,7 +64,7 @@ func TestRuleEval(t *testing.T) {
for _, test := range suite { for _, test := range suite {
rule := NewRecordingRule(test.name, test.expr, test.labels) rule := NewRecordingRule(test.name, test.expr, test.labels)
result, err := rule.Eval(ctx, now, engine, "") result, err := rule.Eval(ctx, now, engine, nil)
if err != nil { if err != nil {
t.Fatalf("Error evaluating %s", test.name) t.Fatalf("Error evaluating %s", test.name)
} }

View file

@ -18,6 +18,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"math" "math"
"net/url"
"regexp" "regexp"
"sort" "sort"
"strings" "strings"
@ -110,7 +111,7 @@ type Expander struct {
} }
// NewTemplateExpander returns a template expander ready to use. // NewTemplateExpander returns a template expander ready to use.
func NewTemplateExpander(ctx context.Context, text string, name string, data interface{}, timestamp model.Time, queryEngine *promql.Engine, pathPrefix string) *Expander { func NewTemplateExpander(ctx context.Context, text string, name string, data interface{}, timestamp model.Time, queryEngine *promql.Engine, externalURL *url.URL) *Expander {
return &Expander{ return &Expander{
text: text, text: text,
name: name, name: name,
@ -246,7 +247,10 @@ func NewTemplateExpander(ctx context.Context, text string, name string, data int
return fmt.Sprint(t) return fmt.Sprint(t)
}, },
"pathPrefix": func() string { "pathPrefix": func() string {
return pathPrefix return externalURL.Path
},
"externalURL": func() string {
return externalURL.String()
}, },
}, },
} }

View file

@ -15,6 +15,7 @@ package template
import ( import (
"math" "math"
"net/url"
"testing" "testing"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
@ -198,6 +199,16 @@ func TestTemplateExpansion(t *testing.T) {
output: "x", output: "x",
html: true, html: true,
}, },
{
// pathPrefix.
text: "{{ pathPrefix }}",
output: "/path/prefix",
},
{
// externalURL.
text: "{{ externalURL }}",
output: "http://testhost:9090/path/prefix",
},
} }
time := model.Time(0) time := model.Time(0)
@ -221,10 +232,15 @@ func TestTemplateExpansion(t *testing.T) {
engine := promql.NewEngine(storage, nil) engine := promql.NewEngine(storage, nil)
extURL, err := url.Parse("http://testhost:9090/path/prefix")
if err != nil {
panic(err)
}
for i, s := range scenarios { for i, s := range scenarios {
var result string var result string
var err error var err error
expander := NewTemplateExpander(context.Background(), s.text, "test", s.input, time, engine, "") expander := NewTemplateExpander(context.Background(), s.text, "test", s.input, time, engine, extURL)
if s.html { if s.html {
result, err = expander.ExpandHTML(nil) result, err = expander.ExpandHTML(nil)
} else { } else {

View file

@ -1,7 +1,7 @@
govalidator govalidator
=========== ===========
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/asaskevich/govalidator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![GoDoc](https://godoc.org/github.com/asaskevich/govalidator?status.png)](https://godoc.org/github.com/asaskevich/govalidator) [![Coverage Status](https://img.shields.io/coveralls/asaskevich/govalidator.svg)](https://coveralls.io/r/asaskevich/govalidator?branch=master) [![wercker status](https://app.wercker.com/status/1ec990b09ea86c910d5f08b0e02c6043/s "wercker status")](https://app.wercker.com/project/bykey/1ec990b09ea86c910d5f08b0e02c6043) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/asaskevich/govalidator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![GoDoc](https://godoc.org/github.com/asaskevich/govalidator?status.png)](https://godoc.org/github.com/asaskevich/govalidator) [![Coverage Status](https://img.shields.io/coveralls/asaskevich/govalidator.svg)](https://coveralls.io/r/asaskevich/govalidator?branch=master) [![wercker status](https://app.wercker.com/status/1ec990b09ea86c910d5f08b0e02c6043/s "wercker status")](https://app.wercker.com/project/bykey/1ec990b09ea86c910d5f08b0e02c6043)
[![Build Status](https://travis-ci.org/asaskevich/govalidator.svg?branch=master)](https://travis-ci.org/asaskevich/govalidator) [![Build Status](https://travis-ci.org/asaskevich/govalidator.svg?branch=master)](https://travis-ci.org/asaskevich/govalidator) [![Go Report Card](https://goreportcard.com/badge/github.com/asaskevich/govalidator)](https://goreportcard.com/report/github.com/asaskevich/govalidator) [![GoSearch](http://go-search.org/badge?id=github.com%2Fasaskevich%2Fgovalidator)](http://go-search.org/view?id=github.com%2Fasaskevich%2Fgovalidator)
A package of validators and sanitizers for strings, structs and collections. Based on [validator.js](https://github.com/chriso/validator.js). A package of validators and sanitizers for strings, structs and collections. Based on [validator.js](https://github.com/chriso/validator.js).
@ -96,28 +96,27 @@ govalidator.CustomTypeTagMap.Set("customByteArrayValidator", CustomTypeValidator
func Abs(value float64) float64 func Abs(value float64) float64
func BlackList(str, chars string) string func BlackList(str, chars string) string
func ByteLength(str string, params ...string) bool func ByteLength(str string, params ...string) bool
func StringLength(str string, params ...string) bool
func StringMatches(s string, params ...string) bool
func CamelCaseToUnderscore(str string) string func CamelCaseToUnderscore(str string) string
func Contains(str, substring string) bool func Contains(str, substring string) bool
func Count(array []interface{}, iterator ConditionIterator) int func Count(array []interface{}, iterator ConditionIterator) int
func Each(array []interface{}, iterator Iterator) func Each(array []interface{}, iterator Iterator)
func ErrorByField(e error, field string) string func ErrorByField(e error, field string) string
func ErrorsByField(e error) map[string]string
func Filter(array []interface{}, iterator ConditionIterator) []interface{} func Filter(array []interface{}, iterator ConditionIterator) []interface{}
func Find(array []interface{}, iterator ConditionIterator) interface{} func Find(array []interface{}, iterator ConditionIterator) interface{}
func GetLine(s string, index int) (string, error) func GetLine(s string, index int) (string, error)
func GetLines(s string) []string func GetLines(s string) []string
func IsHost(s string) bool
func InRange(value, left, right float64) bool func InRange(value, left, right float64) bool
func IsASCII(str string) bool func IsASCII(str string) bool
func IsAlpha(str string) bool func IsAlpha(str string) bool
func IsAlphanumeric(str string) bool func IsAlphanumeric(str string) bool
func IsBase64(str string) bool func IsBase64(str string) bool
func IsByteLength(str string, min, max int) bool func IsByteLength(str string, min, max int) bool
func IsCIDR(str string) bool
func IsCreditCard(str string) bool func IsCreditCard(str string) bool
func IsDNSName(str string) bool
func IsDataURI(str string) bool func IsDataURI(str string) bool
func IsDialString(str string) bool func IsDialString(str string) bool
func IsDNSName(str string) bool
func IsDivisibleBy(str, num string) bool func IsDivisibleBy(str, num string) bool
func IsEmail(str string) bool func IsEmail(str string) bool
func IsFilePath(str string) (bool, int) func IsFilePath(str string) (bool, int)
@ -126,6 +125,7 @@ func IsFullWidth(str string) bool
func IsHalfWidth(str string) bool func IsHalfWidth(str string) bool
func IsHexadecimal(str string) bool func IsHexadecimal(str string) bool
func IsHexcolor(str string) bool func IsHexcolor(str string) bool
func IsHost(str string) bool
func IsIP(str string) bool func IsIP(str string) bool
func IsIPv4(str string) bool func IsIPv4(str string) bool
func IsIPv6(str string) bool func IsIPv6(str string) bool
@ -134,6 +134,8 @@ func IsISBN10(str string) bool
func IsISBN13(str string) bool func IsISBN13(str string) bool
func IsISO3166Alpha2(str string) bool func IsISO3166Alpha2(str string) bool
func IsISO3166Alpha3(str string) bool func IsISO3166Alpha3(str string) bool
func IsISO4217(str string) bool
func IsIn(str string, params ...string) bool
func IsInt(str string) bool func IsInt(str string) bool
func IsJSON(str string) bool func IsJSON(str string) bool
func IsLatitude(str string) bool func IsLatitude(str string) bool
@ -151,11 +153,13 @@ func IsNumeric(str string) bool
func IsPort(str string) bool func IsPort(str string) bool
func IsPositive(value float64) bool func IsPositive(value float64) bool
func IsPrintableASCII(str string) bool func IsPrintableASCII(str string) bool
func IsRFC3339(str string) bool
func IsRGBcolor(str string) bool func IsRGBcolor(str string) bool
func IsRequestURI(rawurl string) bool func IsRequestURI(rawurl string) bool
func IsRequestURL(rawurl string) bool func IsRequestURL(rawurl string) bool
func IsSSN(str string) bool func IsSSN(str string) bool
func IsSemver(str string) bool func IsSemver(str string) bool
func IsTime(str string, format string) bool
func IsURL(str string) bool func IsURL(str string) bool
func IsUTFDigit(str string) bool func IsUTFDigit(str string) bool
func IsUTFLetter(str string) bool func IsUTFLetter(str string) bool
@ -172,12 +176,20 @@ func LeftTrim(str, chars string) string
func Map(array []interface{}, iterator ResultIterator) []interface{} func Map(array []interface{}, iterator ResultIterator) []interface{}
func Matches(str, pattern string) bool func Matches(str, pattern string) bool
func NormalizeEmail(str string) (string, error) 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 Range(str string, params ...string) bool
func RemoveTags(s string) string func RemoveTags(s string) string
func ReplacePattern(str, pattern, replace string) string func ReplacePattern(str, pattern, replace string) string
func Reverse(s string) string func Reverse(s string) string
func RightTrim(str, chars string) string func RightTrim(str, chars string) string
func RuneLength(str string, params ...string) bool
func SafeFileName(str string) string func SafeFileName(str string) string
func SetFieldsRequiredByDefault(value bool)
func Sign(value float64) float64 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 StripLow(str string, keepNewLines bool) string
func ToBoolean(str string) (bool, error) func ToBoolean(str string) (bool, error)
func ToFloat(str string) (float64, error) func ToFloat(str string) (float64, error)
@ -190,10 +202,12 @@ func UnderscoreToCamelCase(s string) string
func ValidateStruct(s interface{}) (bool, error) func ValidateStruct(s interface{}) (bool, error)
func WhiteList(str, chars string) string func WhiteList(str, chars string) string
type ConditionIterator type ConditionIterator
type CustomTypeValidator
type Error type Error
func (e Error) Error() string func (e Error) Error() string
type Errors type Errors
func (es Errors) Error() string func (es Errors) Error() string
func (es Errors) Errors() []error
type ISO3166Entry type ISO3166Entry
type Iterator type Iterator
type ParamValidator type ParamValidator
@ -253,59 +267,65 @@ For completely custom validators (interface-based), see below.
Here is a list of available validators for struct fields (validator - used function): Here is a list of available validators for struct fields (validator - used function):
```go ```go
"alpha": IsAlpha,
"alphanum": IsAlphanumeric,
"ascii": IsASCII,
"base64": IsBase64,
"creditcard": IsCreditCard,
"datauri": IsDataURI,
"dialstring": IsDialString,
"dns": IsDNSName,
"email": IsEmail, "email": IsEmail,
"float": IsFloat, "url": IsURL,
"fullwidth": IsFullWidth, "dialstring": IsDialString,
"halfwidth": IsHalfWidth, "requrl": IsRequestURL,
"requri": IsRequestURI,
"alpha": IsAlpha,
"utfletter": IsUTFLetter,
"alphanum": IsAlphanumeric,
"utfletternum": IsUTFLetterNumeric,
"numeric": IsNumeric,
"utfnumeric": IsUTFNumeric,
"utfdigit": IsUTFDigit,
"hexadecimal": IsHexadecimal, "hexadecimal": IsHexadecimal,
"hexcolor": IsHexcolor, "hexcolor": IsHexcolor,
"host": IsHost,
"int": IsInt,
"ip": IsIP,
"ipv4": IsIPv4,
"ipv6": IsIPv6,
"isbn10": IsISBN10,
"isbn13": IsISBN13,
"json": IsJSON,
"latitude": IsLatitude,
"longitude": IsLongitude,
"lowercase": IsLowerCase,
"mac": IsMAC,
"multibyte": IsMultibyte,
"null": IsNull,
"numeric": IsNumeric,
"port": IsPort,
"printableascii": IsPrintableASCII,
"requri": IsRequestURI,
"requrl": IsRequestURL,
"rgbcolor": IsRGBcolor, "rgbcolor": IsRGBcolor,
"ssn": IsSSN, "lowercase": IsLowerCase,
"semver": IsSemver,
"uppercase": IsUpperCase, "uppercase": IsUpperCase,
"url": IsURL, "int": IsInt,
"utfdigit": IsUTFDigit, "float": IsFloat,
"utfletter": IsUTFLetter, "null": IsNull,
"utfletternum": IsUTFLetterNumeric,
"utfnumeric": IsUTFNumeric,
"uuid": IsUUID, "uuid": IsUUID,
"uuidv3": IsUUIDv3, "uuidv3": IsUUIDv3,
"uuidv4": IsUUIDv4, "uuidv4": IsUUIDv4,
"uuidv5": IsUUIDv5, "uuidv5": IsUUIDv5,
"creditcard": IsCreditCard,
"isbn10": IsISBN10,
"isbn13": IsISBN13,
"json": IsJSON,
"multibyte": IsMultibyte,
"ascii": IsASCII,
"printableascii": IsPrintableASCII,
"fullwidth": IsFullWidth,
"halfwidth": IsHalfWidth,
"variablewidth": IsVariableWidth, "variablewidth": IsVariableWidth,
"base64": IsBase64,
"datauri": IsDataURI,
"ip": IsIP,
"port": IsPort,
"ipv4": IsIPv4,
"ipv6": IsIPv6,
"dns": IsDNSName,
"host": IsHost,
"mac": IsMAC,
"latitude": IsLatitude,
"longitude": IsLongitude,
"ssn": IsSSN,
"semver": IsSemver,
"rfc3339": IsRFC3339,
"ISO3166Alpha2": IsISO3166Alpha2,
"ISO3166Alpha3": IsISO3166Alpha3,
``` ```
Validators with parameters Validators with parameters
```go ```go
"range(min|max)": Range,
"length(min|max)": ByteLength, "length(min|max)": ByteLength,
"runelength(min|max)": RuneLength,
"matches(pattern)": StringMatches, "matches(pattern)": StringMatches,
"in(string1|string2|...|stringN)": IsIn,
``` ```
And here is small example of usage: And here is small example of usage:

View file

@ -4,7 +4,7 @@ import "regexp"
// Basic regular expressions for validating strings // Basic regular expressions for validating strings
const ( 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}])))\\.?$" 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})$" 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})$" ISBN10 string = "^(?:[0-9]{9}X|[0-9]{10})$"
ISBN13 string = "^(?:[0-9]{13})$" ISBN13 string = "^(?:[0-9]{13})$"
@ -14,7 +14,7 @@ const (
UUID string = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[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]+$" Alpha string = "^[a-zA-Z]+$"
Alphanumeric string = "^[a-zA-Z0-9]+$" Alphanumeric string = "^[a-zA-Z0-9]+$"
Numeric string = "^[-+]?[0-9]+$" Numeric string = "^[0-9]+$"
Int string = "^(?:[-+]?(?:0|[1-9][0-9]*))$" Int string = "^(?:[-+]?(?:0|[1-9][0-9]*))$"
Float string = "^(?:[-+]?(?:[0-9]+))?(?:\\.[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$" Float string = "^(?:[-+]?(?:[0-9]+))?(?:\\.[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$"
Hexadecimal string = "^[0-9a-fA-F]+$" Hexadecimal string = "^[0-9a-fA-F]+$"
@ -29,7 +29,7 @@ const (
DataURI string = "^data:.+\\/(.+);base64$" DataURI string = "^data:.+\\/(.+);base64$"
Latitude string = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$" Latitude string = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"
Longitude string = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$" Longitude string = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
DNSName string = `^([a-zA-Z0-9]{1}[a-zA-Z0-9_-]{1,62}){1}(\.[a-zA-Z0-9]{1}[a-zA-Z0-9_-]{1,62})*$` 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]))` 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?):\/\/)` URLSchema string = `((ftp|tcp|udp|wss?|https?):\/\/)`
URLUsername string = `(\S+(:\S*)?@)` URLUsername string = `(\S+(:\S*)?@)`
@ -37,11 +37,11 @@ const (
URLPath string = `((\/|\?|#)[^\s]*)` URLPath string = `((\/|\?|#)[^\s]*)`
URLPort string = `(:(\d{1,5}))` 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]))` 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]+)*))` URLSubdomain string = `((www\.)|([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 + `?$` 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}$` SSN string = `^\d{3}[- ]?\d{2}[- ]?\d{4}$`
WinPath string = `^[a-zA-Z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$` WinPath string = `^[a-zA-Z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$`
UnixPath string = `^((?:\/[a-zA-Z0-9\.\:]+(?:_[a-zA-Z0-9\:\.]+)*(?:\-[\:a-zA-Z0-9\.]+)*)+\/?)$` 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-]+)*)?$" 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" tagName string = "valid"
) )

View file

@ -29,15 +29,21 @@ type stringValues []reflect.Value
// ParamTagMap is a map of functions accept variants parameters // ParamTagMap is a map of functions accept variants parameters
var ParamTagMap = map[string]ParamValidator{ var ParamTagMap = map[string]ParamValidator{
"length": ByteLength, "length": ByteLength,
"range": Range,
"runelength": RuneLength,
"stringlength": StringLength, "stringlength": StringLength,
"matches": StringMatches, "matches": StringMatches,
"in": isInRaw,
} }
// ParamTagRegexMap maps param tags to their respective regexes. // ParamTagRegexMap maps param tags to their respective regexes.
var ParamTagRegexMap = map[string]*regexp.Regexp{ var ParamTagRegexMap = map[string]*regexp.Regexp{
"range": regexp.MustCompile("^range\\((\\d+)\\|(\\d+)\\)$"),
"length": regexp.MustCompile("^length\\((\\d+)\\|(\\d+)\\)$"), "length": regexp.MustCompile("^length\\((\\d+)\\|(\\d+)\\)$"),
"runelength": regexp.MustCompile("^runelength\\((\\d+)\\|(\\d+)\\)$"),
"stringlength": regexp.MustCompile("^stringlength\\((\\d+)\\|(\\d+)\\)$"), "stringlength": regexp.MustCompile("^stringlength\\((\\d+)\\|(\\d+)\\)$"),
"matches": regexp.MustCompile(`matches\(([^)]+)\)`), "in": regexp.MustCompile(`^in\((.*)\)`),
"matches": regexp.MustCompile(`^matches\((.+)\)$`),
} }
type customTypeTagMap struct { type customTypeTagMap struct {
@ -113,6 +119,10 @@ var TagMap = map[string]Validator{
"longitude": IsLongitude, "longitude": IsLongitude,
"ssn": IsSSN, "ssn": IsSSN,
"semver": IsSemver, "semver": IsSemver,
"rfc3339": IsRFC3339,
"ISO3166Alpha2": IsISO3166Alpha2,
"ISO3166Alpha3": IsISO3166Alpha3,
"ISO4217": IsISO4217,
} }
// ISO3166Entry stores country codes // ISO3166Entry stores country codes
@ -376,3 +386,33 @@ var ISO3166List = []ISO3166Entry{
{"Yemen", "Yémen (le)", "YE", "YEM", "887"}, {"Yemen", "Yémen (le)", "YE", "YEM", "887"},
{"Zambia", "Zambie (la)", "ZM", "ZMB", "894"}, {"Zambia", "Zambie (la)", "ZM", "ZMB", "894"},
} }
// ISO4217List is the list of ISO currency codes
var ISO4217List = []string{
"AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN",
"BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BOV", "BRL", "BSD", "BTN", "BWP", "BYN", "BZD",
"CAD", "CDF", "CHE", "CHF", "CHW", "CLF", "CLP", "CNY", "COP", "COU", "CRC", "CUC", "CUP", "CVE", "CZK",
"DJF", "DKK", "DOP", "DZD",
"EGP", "ERN", "ETB", "EUR",
"FJD", "FKP",
"GBP", "GEL", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD",
"HKD", "HNL", "HRK", "HTG", "HUF",
"IDR", "ILS", "INR", "IQD", "IRR", "ISK",
"JMD", "JOD", "JPY",
"KES", "KGS", "KHR", "KMF", "KPW", "KRW", "KWD", "KYD", "KZT",
"LAK", "LBP", "LKR", "LRD", "LSL", "LYD",
"MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRO", "MUR", "MVR", "MWK", "MXN", "MXV", "MYR", "MZN",
"NAD", "NGN", "NIO", "NOK", "NPR", "NZD",
"OMR",
"PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG",
"QAR",
"RON", "RSD", "RUB", "RWF",
"SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLL", "SOS", "SRD", "SSP", "STD", "SVC", "SYP", "SZL",
"THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TWD", "TZS",
"UAH", "UGX", "USD", "USN", "UYI", "UYU", "UZS",
"VEF", "VND", "VUV",
"WST",
"XAF", "XAG", "XAU", "XBA", "XBB", "XBC", "XBD", "XCD", "XDR", "XOF", "XPD", "XPF", "XPT", "XSU", "XTS", "XUA", "XXX",
"YER",
"ZAR", "ZMW", "ZWL",
}

View file

@ -4,10 +4,12 @@ import (
"errors" "errors"
"fmt" "fmt"
"html" "html"
"math"
"path" "path"
"regexp" "regexp"
"strings" "strings"
"unicode" "unicode"
"unicode/utf8"
) )
// Contains check if the string contains the substring. // Contains check if the string contains the substring.
@ -211,3 +213,56 @@ func Truncate(str string, length int, ending string) string {
return str return str
} }
// PadLeft pad left side of 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
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
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
func buildPadStr(str string, padStr string, padLen int, padLeft bool, padRight bool) string {
// When padded length is less then the current string size
if padLen < utf8.RuneCountInString(str) {
return str
}
padLen -= utf8.RuneCountInString(str)
targetLen := padLen
targetLenLeft := targetLen
targetLenRight := targetLen
if padLeft && padRight {
targetLenLeft = padLen / 2
targetLenRight = padLen - targetLenLeft
}
strToRepeatLen := utf8.RuneCountInString(padStr)
repeatTimes := int(math.Ceil(float64(targetLen) / float64(strToRepeatLen)))
repeatedString := strings.Repeat(padStr, repeatTimes)
leftSide := ""
if padLeft {
leftSide = repeatedString[0:targetLenLeft]
}
rightSide := ""
if padRight {
rightSide = repeatedString[0:targetLenRight]
}
return leftSide + str + rightSide
}

View file

@ -11,13 +11,16 @@ import (
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"time"
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
) )
var fieldsRequiredByDefault bool var fieldsRequiredByDefault bool
const maxURLRuneCount = 2083
const minURLRuneCount = 3
// SetFieldsRequiredByDefault causes validation to fail when struct fields // SetFieldsRequiredByDefault causes validation to fail when struct fields
// do not include validations or are not explicitly marked as exempt (using `valid:"-"` or `valid:"email,optional"`). // do not include validations or are not explicitly marked as exempt (using `valid:"-"` or `valid:"email,optional"`).
// This struct definition will fail govalidator.ValidateStruct() (and the field values do not matter): // This struct definition will fail govalidator.ValidateStruct() (and the field values do not matter):
@ -44,7 +47,7 @@ func IsEmail(str string) bool {
// IsURL check if the string is an URL. // IsURL check if the string is an URL.
func IsURL(str string) bool { func IsURL(str string) bool {
if str == "" || len(str) >= 2083 || len(str) <= 3 || strings.HasPrefix(str, ".") { if str == "" || utf8.RuneCountInString(str) >= maxURLRuneCount || len(str) <= minURLRuneCount || strings.HasPrefix(str, ".") {
return false return false
} }
u, err := url.Parse(str) u, err := url.Parse(str)
@ -62,7 +65,7 @@ func IsURL(str string) bool {
} }
// IsRequestURL check if the string rawurl, assuming // IsRequestURL check if the string rawurl, assuming
// it was recieved in an HTTP request, is a valid // it was received in an HTTP request, is a valid
// URL confirm to RFC 3986 // URL confirm to RFC 3986
func IsRequestURL(rawurl string) bool { func IsRequestURL(rawurl string) bool {
url, err := url.ParseRequestURI(rawurl) url, err := url.ParseRequestURI(rawurl)
@ -76,7 +79,7 @@ func IsRequestURL(rawurl string) bool {
} }
// IsRequestURI check if the string rawurl, assuming // IsRequestURI check if the string rawurl, assuming
// it was recieved in an HTTP request, is an // it was received in an HTTP request, is an
// absolute URI or an absolute path. // absolute URI or an absolute path.
func IsRequestURI(rawurl string) bool { func IsRequestURI(rawurl string) bool {
_, err := url.ParseRequestURI(rawurl) _, err := url.ParseRequestURI(rawurl)
@ -458,7 +461,7 @@ func IsDNSName(str string) bool {
// constraints already violated // constraints already violated
return false return false
} }
return rxDNSName.MatchString(str) return !IsIP(str) && rxDNSName.MatchString(str)
} }
// IsDialString validates the given string for usage with the various Dial() functions // IsDialString validates the given string for usage with the various Dial() functions
@ -535,6 +538,17 @@ func IsLongitude(str string) bool {
return rxLongitude.MatchString(str) return rxLongitude.MatchString(str)
} }
func toJSONName(tag string) string {
if tag == "" {
return ""
}
// JSON name always comes first. If there's no options then split[0] is
// JSON name, if JSON name is not set, then split[0] is an empty string.
split := strings.SplitN(tag, ",", 2)
return split[0]
}
// ValidateStruct use tags for fields. // ValidateStruct use tags for fields.
// result will be equal to `false` if there are any errors. // result will be equal to `false` if there are any errors.
func ValidateStruct(s interface{}) (bool, error) { func ValidateStruct(s interface{}) (bool, error) {
@ -558,11 +572,39 @@ func ValidateStruct(s interface{}) (bool, error) {
if typeField.PkgPath != "" { if typeField.PkgPath != "" {
continue // Private field continue // Private field
} }
resultField, err2 := typeCheck(valueField, typeField, val) structResult := true
if valueField.Kind() == reflect.Struct {
var err error
structResult, err = ValidateStruct(valueField.Interface())
if err != nil {
errs = append(errs, err)
}
}
resultField, err2 := typeCheck(valueField, typeField, val, nil)
if err2 != nil { if err2 != nil {
// Replace structure name with JSON name if there is a tag on the variable
jsonTag := toJSONName(typeField.Tag.Get("json"))
if jsonTag != "" {
switch jsonError := err2.(type) {
case Error:
jsonError.Name = jsonTag
err2 = jsonError
case Errors:
for _, e := range jsonError.Errors() {
switch tempErr := e.(type) {
case Error:
tempErr.Name = jsonTag
_ = tempErr
}
}
err2 = jsonError
}
}
errs = append(errs, err2) errs = append(errs, err2)
} }
result = result && resultField result = result && resultField && structResult
} }
if len(errs) > 0 { if len(errs) > 0 {
err = errs err = errs
@ -594,7 +636,7 @@ func isValidTag(s string) bool {
} }
for _, c := range s { for _, c := range s {
switch { switch {
case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c): case strings.ContainsRune("\\'\"!#$%&()*+-./:<=>?@[]^_{|}~ ", c):
// Backslash and quote chars are reserved, but // Backslash and quote chars are reserved, but
// otherwise any punctuation chars are allowed // otherwise any punctuation chars are allowed
// in a tag name. // in a tag name.
@ -620,6 +662,28 @@ func IsSemver(str string) bool {
return rxSemver.MatchString(str) return rxSemver.MatchString(str)
} }
// 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
}
// IsRFC3339 check if string is valid timestamp value according to RFC3339
func IsRFC3339(str string) bool {
return IsTime(str, time.RFC3339)
}
// IsISO4217 check if string is valid ISO currency code
func IsISO4217(str string) bool {
for _, currency := range ISO4217List {
if str == currency {
return true
}
}
return false
}
// ByteLength check string's length // ByteLength check string's length
func ByteLength(str string, params ...string) bool { func ByteLength(str string, params ...string) bool {
if len(params) == 2 { if len(params) == 2 {
@ -631,6 +695,12 @@ func ByteLength(str string, params ...string) bool {
return false return false
} }
// RuneLength check string's length
// Alias for StringLength
func RuneLength(str string, params ...string) bool {
return StringLength(str, params...)
}
// StringMatches checks if a string matches a given pattern. // StringMatches checks if a string matches a given pattern.
func StringMatches(s string, params ...string) bool { func StringMatches(s string, params ...string) bool {
if len(params) == 1 { if len(params) == 1 {
@ -653,6 +723,41 @@ func StringLength(str string, params ...string) bool {
return false return false
} }
// Range check string's length
func Range(str string, params ...string) bool {
if len(params) == 2 {
value, _ := ToFloat(str)
min, _ := ToFloat(params[0])
max, _ := ToFloat(params[1])
return InRange(value, min, max)
}
return false
}
func isInRaw(str string, params ...string) bool {
if len(params) == 1 {
rawParams := params[0]
parsedParams := strings.Split(rawParams, "|")
return IsIn(str, parsedParams...)
}
return false
}
// IsIn check if string str is a member of the set of strings params
func IsIn(str string, params ...string) bool {
for _, param := range params {
if str == param {
return true
}
}
return false
}
func checkRequired(v reflect.Value, t reflect.StructField, options tagOptionsMap) (bool, error) { func checkRequired(v reflect.Value, t reflect.StructField, options tagOptionsMap) (bool, error) {
if requiredOption, isRequired := options["required"]; isRequired { if requiredOption, isRequired := options["required"]; isRequired {
if len(requiredOption) > 0 { if len(requiredOption) > 0 {
@ -666,7 +771,7 @@ func checkRequired(v reflect.Value, t reflect.StructField, options tagOptionsMap
return true, nil return true, nil
} }
func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value) (bool, error) { func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value, options tagOptionsMap) (isValid bool, resultErr error) {
if !v.IsValid() { if !v.IsValid() {
return false, nil return false, nil
} }
@ -684,12 +789,22 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value) (bool, e
return true, nil return true, nil
} }
options := parseTagIntoMap(tag) isRootType := false
if options == nil {
isRootType = true
options = parseTagIntoMap(tag)
}
if isEmptyValue(v) {
// an empty value is not validated, check only required
return checkRequired(v, t, options)
}
var customTypeErrors Errors var customTypeErrors Errors
var customTypeValidatorsExist bool
for validatorName, customErrorMessage := range options { for validatorName, customErrorMessage := range options {
if validatefunc, ok := CustomTypeTagMap.Get(validatorName); ok { if validatefunc, ok := CustomTypeTagMap.Get(validatorName); ok {
customTypeValidatorsExist = true delete(options, validatorName)
if result := validatefunc(v.Interface(), o.Interface()); !result { if result := validatefunc(v.Interface(), o.Interface()); !result {
if len(customErrorMessage) > 0 { if len(customErrorMessage) > 0 {
customTypeErrors = append(customTypeErrors, Error{Name: t.Name, Err: fmt.Errorf(customErrorMessage), CustomErrorMessageExists: true}) customTypeErrors = append(customTypeErrors, Error{Name: t.Name, Err: fmt.Errorf(customErrorMessage), CustomErrorMessageExists: true})
@ -699,16 +814,26 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value) (bool, e
} }
} }
} }
if customTypeValidatorsExist {
if len(customTypeErrors.Errors()) > 0 { if len(customTypeErrors.Errors()) > 0 {
return false, customTypeErrors return false, customTypeErrors
}
return true, nil
} }
if isEmptyValue(v) { if isRootType {
// an empty value is not validated, check only required // Ensure that we've checked the value by all specified validators before report that the value is valid
return checkRequired(v, t, options) defer func() {
delete(options, "optional")
delete(options, "required")
if isValid && resultErr == nil && len(options) != 0 {
for validator := range options {
isValid = false
resultErr = Error{t.Name, fmt.Errorf(
"The following validator is invalid or can't be applied to the field: %q", validator), false}
return
}
}
}()
} }
switch v.Kind() { switch v.Kind() {
@ -718,10 +843,12 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value) (bool, e
reflect.Float32, reflect.Float64, reflect.Float32, reflect.Float64,
reflect.String: reflect.String:
// for each tag option check the map of validator functions // for each tag option check the map of validator functions
for validator, customErrorMessage := range options { for validatorSpec, customErrorMessage := range options {
var negate bool var negate bool
validator := validatorSpec
customMsgExists := (len(customErrorMessage) > 0) customMsgExists := (len(customErrorMessage) > 0)
// Check wether the tag looks like '!something' or 'something'
// Check whether the tag looks like '!something' or 'something'
if validator[0] == '!' { if validator[0] == '!' {
validator = string(validator[1:]) validator = string(validator[1:])
negate = true negate = true
@ -730,38 +857,47 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value) (bool, e
// Check for param validators // Check for param validators
for key, value := range ParamTagRegexMap { for key, value := range ParamTagRegexMap {
ps := value.FindStringSubmatch(validator) ps := value.FindStringSubmatch(validator)
if len(ps) > 0 { if len(ps) == 0 {
if validatefunc, ok := ParamTagMap[key]; ok { continue
switch v.Kind() { }
case reflect.String:
field := fmt.Sprint(v) // make value into string, then validate with regex
if result := validatefunc(field, ps[1:]...); (!result && !negate) || (result && negate) {
var err error
if !negate {
if customMsgExists {
err = fmt.Errorf(customErrorMessage)
} else {
err = fmt.Errorf("%s does not validate as %s", field, validator)
}
} else { validatefunc, ok := ParamTagMap[key]
if customMsgExists { if !ok {
err = fmt.Errorf(customErrorMessage) continue
} else { }
err = fmt.Errorf("%s does validate as %s", field, validator)
} delete(options, validatorSpec)
}
return false, Error{t.Name, err, customMsgExists} switch v.Kind() {
case reflect.String:
field := fmt.Sprint(v) // make value into string, then validate with regex
if result := validatefunc(field, ps[1:]...); (!result && !negate) || (result && negate) {
var err error
if !negate {
if customMsgExists {
err = fmt.Errorf(customErrorMessage)
} else {
err = fmt.Errorf("%s does not validate as %s", field, validator)
}
} else {
if customMsgExists {
err = fmt.Errorf(customErrorMessage)
} else {
err = fmt.Errorf("%s does validate as %s", field, validator)
} }
default:
// type not yet supported, fail
return false, Error{t.Name, fmt.Errorf("Validator %s doesn't support kind %s", validator, v.Kind()), false}
} }
return false, Error{t.Name, err, customMsgExists}
} }
default:
// type not yet supported, fail
return false, Error{t.Name, fmt.Errorf("Validator %s doesn't support kind %s", validator, v.Kind()), false}
} }
} }
if validatefunc, ok := TagMap[validator]; ok { if validatefunc, ok := TagMap[validator]; ok {
delete(options, validatorSpec)
switch v.Kind() { switch v.Kind() {
case reflect.String: case reflect.String:
field := fmt.Sprint(v) // make value into string, then validate with regex field := fmt.Sprint(v) // make value into string, then validate with regex
@ -813,7 +949,7 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value) (bool, e
var resultItem bool var resultItem bool
var err error var err error
if v.Index(i).Kind() != reflect.Struct { if v.Index(i).Kind() != reflect.Struct {
resultItem, err = typeCheck(v.Index(i), t, o) resultItem, err = typeCheck(v.Index(i), t, o, options)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -832,7 +968,7 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value) (bool, e
var resultItem bool var resultItem bool
var err error var err error
if v.Index(i).Kind() != reflect.Struct { if v.Index(i).Kind() != reflect.Struct {
resultItem, err = typeCheck(v.Index(i), t, o) resultItem, err = typeCheck(v.Index(i), t, o, options)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -856,7 +992,7 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value) (bool, e
if v.IsNil() { if v.IsNil() {
return true, nil return true, nil
} }
return typeCheck(v.Elem(), t, o) return typeCheck(v.Elem(), t, o, options)
case reflect.Struct: case reflect.Struct:
return ValidateStruct(v.Interface()) return ValidateStruct(v.Interface())
default: default:

View file

@ -1,4 +1,4 @@
box: wercker/golang box: golang
build: build:
steps: steps:
- setup-go-workspace - setup-go-workspace

6
vendor/vendor.json vendored
View file

@ -59,10 +59,10 @@
"revisionTime": "2016-09-30T00:14:02Z" "revisionTime": "2016-09-30T00:14:02Z"
}, },
{ {
"checksumSHA1": "BdLdZP/C2uOO3lqk9X3NCKFpXa4=", "checksumSHA1": "ddYc7mKe3g1x1UUKBrGR4vArJs8=",
"path": "github.com/asaskevich/govalidator", "path": "github.com/asaskevich/govalidator",
"revision": "7b3beb6df3c42abd3509abfc3bcacc0fbfb7c877", "revision": "065ea97278837088c52c0cd0d963473f61b2d98c",
"revisionTime": "2016-10-01T16:31:30Z" "revisionTime": "2017-05-13T08:31:01Z"
}, },
{ {
"checksumSHA1": "WNfR3yhLjRC5/uccgju/bwrdsxQ=", "checksumSHA1": "WNfR3yhLjRC5/uccgju/bwrdsxQ=",

View file

@ -327,7 +327,7 @@ func (h *Handler) consoles(w http.ResponseWriter, r *http.Request) {
Path: strings.TrimLeft(name, "/"), Path: strings.TrimLeft(name, "/"),
} }
tmpl := template.NewTemplateExpander(h.context, string(text), "__console_"+name, data, h.now(), h.queryEngine, h.options.ExternalURL.Path) tmpl := template.NewTemplateExpander(h.context, string(text), "__console_"+name, data, h.now(), h.queryEngine, h.options.ExternalURL)
filenames, err := filepath.Glob(h.options.ConsoleLibrariesPath + "/*.lib") filenames, err := filepath.Glob(h.options.ConsoleLibrariesPath + "/*.lib")
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
@ -522,7 +522,7 @@ func (h *Handler) executeTemplate(w http.ResponseWriter, name string, data inter
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
} }
tmpl := template.NewTemplateExpander(h.context, text, name, data, h.now(), h.queryEngine, h.options.ExternalURL.Path) tmpl := template.NewTemplateExpander(h.context, text, name, data, h.now(), h.queryEngine, h.options.ExternalURL)
tmpl.Funcs(tmplFuncs(h.consolesPath(), h.options)) tmpl.Funcs(tmplFuncs(h.consolesPath(), h.options))
result, err := tmpl.ExpandHTML(nil) result, err := tmpl.ExpandHTML(nil)