From ac203ef0ee2e6bebec6bef2dd7057036b62adedf Mon Sep 17 00:00:00 2001 From: Julius Volz Date: Sat, 13 May 2017 15:47:04 +0200 Subject: [PATCH 1/4] Add externalURL template function (#2716) This allows users to e.g. add links back to the generating Prometheus right in their alert templates. --- rules/alerting.go | 5 +++-- rules/manager.go | 4 ++-- rules/manager_test.go | 2 +- rules/recording.go | 3 ++- rules/recording_test.go | 2 +- template/template.go | 8 ++++++-- template/template_test.go | 18 +++++++++++++++++- web/web.go | 4 ++-- 8 files changed, 34 insertions(+), 12 deletions(-) diff --git a/rules/alerting.go b/rules/alerting.go index 514b0a7ddb..6a489cfa8e 100644 --- a/rules/alerting.go +++ b/rules/alerting.go @@ -15,6 +15,7 @@ package rules import ( "fmt" + "net/url" "sync" "time" @@ -148,7 +149,7 @@ const resolvedRetention = 15 * time.Minute // Eval evaluates the rule expression and then creates pending alerts and fires // or removes previously pending alerts accordingly. -func (r *AlertingRule) Eval(ctx context.Context, ts model.Time, engine *promql.Engine, externalURLPath string) (model.Vector, error) { +func (r *AlertingRule) Eval(ctx context.Context, ts model.Time, engine *promql.Engine, externalURL *url.URL) (model.Vector, error) { query, err := engine.NewInstantQuery(r.vector.String(), ts) if err != nil { return nil, err @@ -191,7 +192,7 @@ func (r *AlertingRule) Eval(ctx context.Context, ts model.Time, engine *promql.E tmplData, ts, engine, - externalURLPath, + externalURL, ) result, err := tmpl.Expand() if err != nil { diff --git a/rules/manager.go b/rules/manager.go index 09d19a5da5..8b017c55ad 100644 --- a/rules/manager.go +++ b/rules/manager.go @@ -113,7 +113,7 @@ const ( type Rule interface { Name() string // eval evaluates the rule, including any associated recording or alerting actions. - Eval(context.Context, model.Time, *promql.Engine, string) (model.Vector, error) + Eval(context.Context, model.Time, *promql.Engine, *url.URL) (model.Vector, error) // String returns a human-readable string representation of the rule. String() string // HTMLSnippet returns a human-readable string representation of the rule, @@ -274,7 +274,7 @@ func (g *Group) Eval() { 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 { // Canceled queries are intentional termination of queries. This normally // happens on shutdown and thus we skip logging of any errors here. diff --git a/rules/manager_test.go b/rules/manager_test.go index d229f8b961..e8e26fad44 100644 --- a/rules/manager_test.go +++ b/rules/manager_test.go @@ -105,7 +105,7 @@ func TestAlertingRule(t *testing.T) { for i, test := range tests { evalTime := model.Time(0).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 { t.Fatalf("Error during alerting rule evaluation: %s", err) } diff --git a/rules/recording.go b/rules/recording.go index a4b7a4b8de..70fd75582d 100644 --- a/rules/recording.go +++ b/rules/recording.go @@ -16,6 +16,7 @@ package rules import ( "fmt" "html/template" + "net/url" "github.com/prometheus/common/model" "golang.org/x/net/context" @@ -46,7 +47,7 @@ func (rule RecordingRule) Name() string { } // Eval evaluates the rule and then overrides the metric names and labels accordingly. -func (rule RecordingRule) Eval(ctx context.Context, timestamp model.Time, engine *promql.Engine, _ string) (model.Vector, error) { +func (rule RecordingRule) Eval(ctx context.Context, timestamp model.Time, engine *promql.Engine, _ *url.URL) (model.Vector, error) { query, err := engine.NewInstantQuery(rule.vector.String(), timestamp) if err != nil { return nil, err diff --git a/rules/recording_test.go b/rules/recording_test.go index 00b9308c14..472eeef4d2 100644 --- a/rules/recording_test.go +++ b/rules/recording_test.go @@ -63,7 +63,7 @@ func TestRuleEval(t *testing.T) { for _, test := range suite { 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 { t.Fatalf("Error evaluating %s", test.name) } diff --git a/template/template.go b/template/template.go index f468b9b6ed..67e2ba073a 100644 --- a/template/template.go +++ b/template/template.go @@ -18,6 +18,7 @@ import ( "errors" "fmt" "math" + "net/url" "regexp" "sort" "strings" @@ -111,7 +112,7 @@ type Expander struct { } // 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{ text: text, name: name, @@ -247,7 +248,10 @@ func NewTemplateExpander(ctx context.Context, text string, name string, data int return fmt.Sprint(t) }, "pathPrefix": func() string { - return pathPrefix + return externalURL.Path + }, + "externalURL": func() string { + return externalURL.String() }, }, } diff --git a/template/template_test.go b/template/template_test.go index 3f919ab1b6..4e2031d32d 100644 --- a/template/template_test.go +++ b/template/template_test.go @@ -15,6 +15,7 @@ package template import ( "math" + "net/url" "testing" "github.com/prometheus/common/model" @@ -196,6 +197,16 @@ func TestTemplateExpansion(t *testing.T) { output: "x", html: true, }, + { + // pathPrefix. + text: "{{ pathPrefix }}", + output: "/path/prefix", + }, + { + // externalURL. + text: "{{ externalURL }}", + output: "http://testhost:9090/path/prefix", + }, } time := model.Time(0) @@ -218,10 +229,15 @@ func TestTemplateExpansion(t *testing.T) { engine := promql.NewEngine(storage, nil) + extURL, err := url.Parse("http://testhost:9090/path/prefix") + if err != nil { + panic(err) + } + for i, s := range scenarios { var result string 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 { result, err = expander.ExpandHTML(nil) } else { diff --git a/web/web.go b/web/web.go index 425f56bae4..7cac4effce 100644 --- a/web/web.go +++ b/web/web.go @@ -326,7 +326,7 @@ func (h *Handler) consoles(w http.ResponseWriter, r *http.Request) { 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") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -521,7 +521,7 @@ func (h *Handler) executeTemplate(w http.ResponseWriter, name string, data inter 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)) result, err := tmpl.ExpandHTML(nil) From 61235fd851f08ae85f52775e3352d251b52d0fb2 Mon Sep 17 00:00:00 2001 From: Shashank Varanasi Date: Sun, 14 May 2017 00:12:29 +0530 Subject: [PATCH 2/4] Print system information (uname) at Prometheus startup (#2709) * Print uname on prom startup * Make uname file linux-only * Add missing license headers Add missing license headers * Print OS when uname is not available * Print only OS name when uname not available * Remove extra space, fix cmd/prometheus/main.go license header * Add fix for int8 and uint8 systems * Better formatting for build tags in cmd/prometheus/uname files * Remove newline --- cmd/prometheus/main.go | 1 + cmd/prometheus/uname_default.go | 23 +++++++++++++++++++ cmd/prometheus/uname_linux.go | 35 +++++++++++++++++++++++++++++ cmd/prometheus/uname_linux_int8.go | 25 +++++++++++++++++++++ cmd/prometheus/uname_linux_uint8.go | 25 +++++++++++++++++++++ 5 files changed, 109 insertions(+) create mode 100644 cmd/prometheus/uname_default.go create mode 100644 cmd/prometheus/uname_linux.go create mode 100644 cmd/prometheus/uname_linux_int8.go create mode 100644 cmd/prometheus/uname_linux_uint8.go diff --git a/cmd/prometheus/main.go b/cmd/prometheus/main.go index 3b5ff26267..53d2fe12cc 100644 --- a/cmd/prometheus/main.go +++ b/cmd/prometheus/main.go @@ -87,6 +87,7 @@ func Main() int { log.Infoln("Starting prometheus", version.Info()) log.Infoln("Build context", version.BuildContext()) + log.Infoln("Host details", Uname()) var ( sampleAppender = storage.Fanout{} diff --git a/cmd/prometheus/uname_default.go b/cmd/prometheus/uname_default.go new file mode 100644 index 0000000000..bc7f0502cf --- /dev/null +++ b/cmd/prometheus/uname_default.go @@ -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 + ")" +} diff --git a/cmd/prometheus/uname_linux.go b/cmd/prometheus/uname_linux.go new file mode 100644 index 0000000000..8653568161 --- /dev/null +++ b/cmd/prometheus/uname_linux.go @@ -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 +} diff --git a/cmd/prometheus/uname_linux_int8.go b/cmd/prometheus/uname_linux_int8.go new file mode 100644 index 0000000000..40cfb57536 --- /dev/null +++ b/cmd/prometheus/uname_linux_int8.go @@ -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)]) +} diff --git a/cmd/prometheus/uname_linux_uint8.go b/cmd/prometheus/uname_linux_uint8.go new file mode 100644 index 0000000000..da5794caa2 --- /dev/null +++ b/cmd/prometheus/uname_linux_uint8.go @@ -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)]) +} From b0e1ea7c6c27f0a0c5cdfab01225f9d5e6eb589b Mon Sep 17 00:00:00 2001 From: Alexey Palazhchenko Date: Mon, 15 May 2017 11:56:09 +0300 Subject: [PATCH 3/4] Simplify code, fix typos. (#2719) --- rules/alerting_test.go | 2 +- storage/remote/queue_manager.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rules/alerting_test.go b/rules/alerting_test.go index c06a40c9f8..3f7d4953f5 100644 --- a/rules/alerting_test.go +++ b/rules/alerting_test.go @@ -41,7 +41,7 @@ func TestAlertingRuleHTMLSnippet(t *testing.T) { func TestCurrentAlertsClonesLabelsAndAnnotations(t *testing.T) { r := AlertingRule{ active: map[model.Fingerprint]*Alert{ - 0: &Alert{ + 0: { Labels: model.LabelSet{"test_label": "test_label_value"}, Annotations: model.LabelSet{"test_annotation": "test_annotation_value"}, }, diff --git a/storage/remote/queue_manager.go b/storage/remote/queue_manager.go index 6813566a4a..40c5a5eb40 100644 --- a/storage/remote/queue_manager.go +++ b/storage/remote/queue_manager.go @@ -323,7 +323,7 @@ func (t *QueueManager) calculateDesiredShards() { timePerSample = samplesOutDuration / samplesOut desiredShards = (timePerSample * (samplesIn + samplesPending + t.integralAccumulator)) / float64(time.Second) ) - log.Debugf("QueueManager.caclulateDesiredShards samplesIn=%f, samplesOut=%f, samplesPending=%f, desiredShards=%f", + log.Debugf("QueueManager.calculateDesiredShards samplesIn=%f, samplesOut=%f, samplesPending=%f, desiredShards=%f", samplesIn, samplesOut, samplesPending, desiredShards) // Changes in the number of shards must be greater than shardToleranceFraction. @@ -478,7 +478,7 @@ func (s *shards) sendSamples(samples model.Samples) { begin := time.Now() s.sendSamplesWithBackoff(samples) - // These counters are used to caclulate the dynamic sharding, and as such + // These counters are used to calculate the dynamic sharding, and as such // should be maintained irrespective of success or failure. s.qm.samplesOut.incr(int64(len(samples))) s.qm.samplesOutDuration.incr(int64(time.Since(begin))) From 713fef292a83d11489656982faeea3059f99e4ee Mon Sep 17 00:00:00 2001 From: jswitzer Date: Mon, 15 May 2017 08:35:57 -0400 Subject: [PATCH 4/4] vendor: upgrade govalidator to v6 (#2714) * vendor: upgrade govalidator to v6 * Upgrade govalidator to latest in master. * Run govalidator update through govendor --- .../asaskevich/govalidator/README.md | 104 ++++---- .../asaskevich/govalidator/patterns.go | 12 +- .../asaskevich/govalidator/types.go | 42 +++- .../asaskevich/govalidator/utils.go | 55 ++++ .../asaskevich/govalidator/validator.go | 234 ++++++++++++++---- .../asaskevich/govalidator/wercker.yml | 2 +- vendor/vendor.json | 6 +- 7 files changed, 353 insertions(+), 102 deletions(-) diff --git a/vendor/github.com/asaskevich/govalidator/README.md b/vendor/github.com/asaskevich/govalidator/README.md index 57d85fd410..19c188af2e 100644 --- a/vendor/github.com/asaskevich/govalidator/README.md +++ b/vendor/github.com/asaskevich/govalidator/README.md @@ -1,7 +1,7 @@ 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) -[![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). @@ -96,28 +96,27 @@ govalidator.CustomTypeTagMap.Set("customByteArrayValidator", CustomTypeValidator func Abs(value float64) float64 func BlackList(str, chars string) string 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 Contains(str, substring string) bool func Count(array []interface{}, iterator ConditionIterator) int func Each(array []interface{}, iterator Iterator) func ErrorByField(e error, field string) string +func ErrorsByField(e error) map[string]string 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 IsHost(s string) bool func InRange(value, left, right float64) 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 IsCreditCard(str string) bool +func IsDNSName(str string) bool func IsDataURI(str string) bool func IsDialString(str string) bool -func IsDNSName(str string) bool func IsDivisibleBy(str, num string) bool func IsEmail(str string) bool func IsFilePath(str string) (bool, int) @@ -126,6 +125,7 @@ func IsFullWidth(str string) bool func IsHalfWidth(str string) bool func IsHexadecimal(str string) bool func IsHexcolor(str string) bool +func IsHost(str string) bool func IsIP(str string) bool func IsIPv4(str string) bool func IsIPv6(str string) bool @@ -134,6 +134,8 @@ 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 IsIn(str string, params ...string) bool func IsInt(str string) bool func IsJSON(str string) bool func IsLatitude(str string) bool @@ -151,11 +153,13 @@ func IsNumeric(str string) bool func IsPort(str string) bool func IsPositive(value float64) bool func IsPrintableASCII(str string) bool +func IsRFC3339(str string) bool func IsRGBcolor(str string) bool func IsRequestURI(rawurl string) bool func IsRequestURL(rawurl string) bool func IsSSN(str string) bool func IsSemver(str string) bool +func IsTime(str string, format string) bool func IsURL(str string) bool func IsUTFDigit(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 Matches(str, pattern 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 Range(str string, params ...string) bool func RemoveTags(s string) string func ReplacePattern(str, pattern, replace string) string func Reverse(s string) string func RightTrim(str, chars string) string +func RuneLength(str string, params ...string) bool func SafeFileName(str string) string +func SetFieldsRequiredByDefault(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) @@ -190,10 +202,12 @@ func UnderscoreToCamelCase(s string) string func ValidateStruct(s interface{}) (bool, error) func WhiteList(str, chars string) string type ConditionIterator +type CustomTypeValidator type Error func (e Error) Error() string type Errors func (es Errors) Error() string +func (es Errors) Errors() []error type ISO3166Entry type Iterator 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): ```go -"alpha": IsAlpha, -"alphanum": IsAlphanumeric, -"ascii": IsASCII, -"base64": IsBase64, -"creditcard": IsCreditCard, -"datauri": IsDataURI, -"dialstring": IsDialString, -"dns": IsDNSName, "email": IsEmail, -"float": IsFloat, -"fullwidth": IsFullWidth, -"halfwidth": IsHalfWidth, +"url": IsURL, +"dialstring": IsDialString, +"requrl": IsRequestURL, +"requri": IsRequestURI, +"alpha": IsAlpha, +"utfletter": IsUTFLetter, +"alphanum": IsAlphanumeric, +"utfletternum": IsUTFLetterNumeric, +"numeric": IsNumeric, +"utfnumeric": IsUTFNumeric, +"utfdigit": IsUTFDigit, "hexadecimal": IsHexadecimal, "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, -"ssn": IsSSN, -"semver": IsSemver, +"lowercase": IsLowerCase, "uppercase": IsUpperCase, -"url": IsURL, -"utfdigit": IsUTFDigit, -"utfletter": IsUTFLetter, -"utfletternum": IsUTFLetterNumeric, -"utfnumeric": IsUTFNumeric, +"int": IsInt, +"float": IsFloat, +"null": IsNull, "uuid": IsUUID, "uuidv3": IsUUIDv3, "uuidv4": IsUUIDv4, "uuidv5": IsUUIDv5, +"creditcard": IsCreditCard, +"isbn10": IsISBN10, +"isbn13": IsISBN13, +"json": IsJSON, +"multibyte": IsMultibyte, +"ascii": IsASCII, +"printableascii": IsPrintableASCII, +"fullwidth": IsFullWidth, +"halfwidth": IsHalfWidth, "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 ```go +"range(min|max)": Range, "length(min|max)": ByteLength, +"runelength(min|max)": RuneLength, "matches(pattern)": StringMatches, +"in(string1|string2|...|stringN)": IsIn, ``` And here is small example of usage: diff --git a/vendor/github.com/asaskevich/govalidator/patterns.go b/vendor/github.com/asaskevich/govalidator/patterns.go index 2aa41abe84..e15c18a36b 100644 --- a/vendor/github.com/asaskevich/govalidator/patterns.go +++ b/vendor/github.com/asaskevich/govalidator/patterns.go @@ -4,7 +4,7 @@ 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}])))\\.?$" + 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})$" @@ -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}$" Alpha string = "^[a-zA-Z]+$" Alphanumeric string = "^[a-zA-Z0-9]+$" - Numeric string = "^[-+]?[0-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]+$" @@ -29,7 +29,7 @@ const ( 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_-]{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]))` URLSchema string = `((ftp|tcp|udp|wss?|https?):\/\/)` URLUsername string = `(\S+(:\S*)?@)` @@ -37,11 +37,11 @@ const ( 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]+)*))` - 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 + `?$` + 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 + `?$` SSN string = `^\d{3}[- ]?\d{2}[- ]?\d{4}$` 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-]+)*)?$" tagName string = "valid" ) diff --git a/vendor/github.com/asaskevich/govalidator/types.go b/vendor/github.com/asaskevich/govalidator/types.go index b715612762..f79b232149 100644 --- a/vendor/github.com/asaskevich/govalidator/types.go +++ b/vendor/github.com/asaskevich/govalidator/types.go @@ -29,15 +29,21 @@ type stringValues []reflect.Value // 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, } // ParamTagRegexMap maps param tags to their respective regexes. var ParamTagRegexMap = map[string]*regexp.Regexp{ + "range": regexp.MustCompile("^range\\((\\d+)\\|(\\d+)\\)$"), "length": regexp.MustCompile("^length\\((\\d+)\\|(\\d+)\\)$"), + "runelength": regexp.MustCompile("^runelength\\((\\d+)\\|(\\d+)\\)$"), "stringlength": regexp.MustCompile("^stringlength\\((\\d+)\\|(\\d+)\\)$"), - "matches": regexp.MustCompile(`matches\(([^)]+)\)`), + "in": regexp.MustCompile(`^in\((.*)\)`), + "matches": regexp.MustCompile(`^matches\((.+)\)$`), } type customTypeTagMap struct { @@ -113,6 +119,10 @@ var TagMap = map[string]Validator{ "longitude": IsLongitude, "ssn": IsSSN, "semver": IsSemver, + "rfc3339": IsRFC3339, + "ISO3166Alpha2": IsISO3166Alpha2, + "ISO3166Alpha3": IsISO3166Alpha3, + "ISO4217": IsISO4217, } // ISO3166Entry stores country codes @@ -376,3 +386,33 @@ var ISO3166List = []ISO3166Entry{ {"Yemen", "YƩmen (le)", "YE", "YEM", "887"}, {"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", +} diff --git a/vendor/github.com/asaskevich/govalidator/utils.go b/vendor/github.com/asaskevich/govalidator/utils.go index 200b9131de..040d7bdb78 100644 --- a/vendor/github.com/asaskevich/govalidator/utils.go +++ b/vendor/github.com/asaskevich/govalidator/utils.go @@ -4,10 +4,12 @@ import ( "errors" "fmt" "html" + "math" "path" "regexp" "strings" "unicode" + "unicode/utf8" ) // Contains check if the string contains the substring. @@ -211,3 +213,56 @@ 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 +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 +} diff --git a/vendor/github.com/asaskevich/govalidator/validator.go b/vendor/github.com/asaskevich/govalidator/validator.go index 9cd5148e98..d1379a32d9 100644 --- a/vendor/github.com/asaskevich/govalidator/validator.go +++ b/vendor/github.com/asaskevich/govalidator/validator.go @@ -11,13 +11,16 @@ import ( "sort" "strconv" "strings" - + "time" "unicode" "unicode/utf8" ) var fieldsRequiredByDefault bool +const maxURLRuneCount = 2083 +const minURLRuneCount = 3 + // 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"`). // 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. 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 } u, err := url.Parse(str) @@ -62,7 +65,7 @@ func IsURL(str string) bool { } // 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 func IsRequestURL(rawurl string) bool { url, err := url.ParseRequestURI(rawurl) @@ -76,7 +79,7 @@ func IsRequestURL(rawurl string) bool { } // 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. func IsRequestURI(rawurl string) bool { _, err := url.ParseRequestURI(rawurl) @@ -458,7 +461,7 @@ func IsDNSName(str string) bool { // constraints already violated return false } - return rxDNSName.MatchString(str) + return !IsIP(str) && rxDNSName.MatchString(str) } // 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) } +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. // result will be equal to `false` if there are any errors. func ValidateStruct(s interface{}) (bool, error) { @@ -558,11 +572,39 @@ func ValidateStruct(s interface{}) (bool, error) { if typeField.PkgPath != "" { 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 { + + // 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) } - result = result && resultField + result = result && resultField && structResult } if len(errs) > 0 { err = errs @@ -594,7 +636,7 @@ func isValidTag(s string) bool { } for _, c := range s { switch { - case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c): + case strings.ContainsRune("\\'\"!#$%&()*+-./:<=>?@[]^_{|}~ ", c): // Backslash and quote chars are reserved, but // otherwise any punctuation chars are allowed // in a tag name. @@ -620,6 +662,28 @@ func IsSemver(str string) bool { 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 func ByteLength(str string, params ...string) bool { if len(params) == 2 { @@ -631,6 +695,12 @@ func ByteLength(str string, params ...string) bool { 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. func StringMatches(s string, params ...string) bool { if len(params) == 1 { @@ -653,6 +723,41 @@ func StringLength(str string, params ...string) bool { 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) { if requiredOption, isRequired := options["required"]; isRequired { if len(requiredOption) > 0 { @@ -666,7 +771,7 @@ func checkRequired(v reflect.Value, t reflect.StructField, options tagOptionsMap 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() { return false, nil } @@ -684,12 +789,22 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value) (bool, e 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 customTypeValidatorsExist bool for validatorName, customErrorMessage := range options { if validatefunc, ok := CustomTypeTagMap.Get(validatorName); ok { - customTypeValidatorsExist = true + delete(options, validatorName) + if result := validatefunc(v.Interface(), o.Interface()); !result { if len(customErrorMessage) > 0 { 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 { - return false, customTypeErrors - } - return true, nil + + if len(customTypeErrors.Errors()) > 0 { + return false, customTypeErrors } - if isEmptyValue(v) { - // an empty value is not validated, check only required - return checkRequired(v, t, options) + if isRootType { + // Ensure that we've checked the value by all specified validators before report that the value is valid + 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() { @@ -718,10 +843,12 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value) (bool, e reflect.Float32, reflect.Float64, reflect.String: // for each tag option check the map of validator functions - for validator, customErrorMessage := range options { + for validatorSpec, customErrorMessage := range options { var negate bool + validator := validatorSpec 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] == '!' { validator = string(validator[1:]) negate = true @@ -730,38 +857,47 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value) (bool, e // Check for param validators for key, value := range ParamTagRegexMap { ps := value.FindStringSubmatch(validator) - if len(ps) > 0 { - if validatefunc, ok := ParamTagMap[key]; ok { - 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) - } + if len(ps) == 0 { + continue + } - } else { - if customMsgExists { - err = fmt.Errorf(customErrorMessage) - } else { - err = fmt.Errorf("%s does validate as %s", field, validator) - } - } - return false, Error{t.Name, err, customMsgExists} + validatefunc, ok := ParamTagMap[key] + if !ok { + continue + } + + delete(options, validatorSpec) + + 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 { + delete(options, validatorSpec) + switch v.Kind() { case reflect.String: 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 err error 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 { return false, err } @@ -832,7 +968,7 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value) (bool, e var resultItem bool var err error 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 { return false, err } @@ -856,7 +992,7 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value) (bool, e if v.IsNil() { return true, nil } - return typeCheck(v.Elem(), t, o) + return typeCheck(v.Elem(), t, o, options) case reflect.Struct: return ValidateStruct(v.Interface()) default: diff --git a/vendor/github.com/asaskevich/govalidator/wercker.yml b/vendor/github.com/asaskevich/govalidator/wercker.yml index 4840449099..cac7a5fcf0 100644 --- a/vendor/github.com/asaskevich/govalidator/wercker.yml +++ b/vendor/github.com/asaskevich/govalidator/wercker.yml @@ -1,4 +1,4 @@ -box: wercker/golang +box: golang build: steps: - setup-go-workspace diff --git a/vendor/vendor.json b/vendor/vendor.json index d348f118bf..2018556ce1 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -59,10 +59,10 @@ "revisionTime": "2016-09-30T00:14:02Z" }, { - "checksumSHA1": "BdLdZP/C2uOO3lqk9X3NCKFpXa4=", + "checksumSHA1": "ddYc7mKe3g1x1UUKBrGR4vArJs8=", "path": "github.com/asaskevich/govalidator", - "revision": "7b3beb6df3c42abd3509abfc3bcacc0fbfb7c877", - "revisionTime": "2016-10-01T16:31:30Z" + "revision": "065ea97278837088c52c0cd0d963473f61b2d98c", + "revisionTime": "2017-05-13T08:31:01Z" }, { "checksumSHA1": "WNfR3yhLjRC5/uccgju/bwrdsxQ=",