Merge pull request #3243 from prometheus/mergemaster

Merge master into dev-2.0
This commit is contained in:
Fabian Reinartz 2017-10-05 14:32:52 +02:00 committed by GitHub
commit 46dc7e356a
37 changed files with 466 additions and 2739 deletions

View file

@ -3,7 +3,7 @@ sudo: false
language: go language: go
go: go:
- 1.8.x - 1.9.x
- 1.x - 1.x
go_import_path: github.com/prometheus/prometheus go_import_path: github.com/prometheus/prometheus

View file

@ -1,3 +1,16 @@
## 1.7.2 / 2017-09-26
* [BUGFIX] Correctly remove all targets from DNS service discovery if the
corresponding DNS query succeeds and returns an empty result.
* [BUGFIX] Correctly parse resolution input in expression browser.
* [BUGFIX] Consistently use UTC in the date picker of the expression browser.
* [BUGFIX] Correctly handle multiple ports in Marathon service discovery.
* [BUGFIX] Fix HTML escaping so that HTML templates compile with Go1.9.
* [BUGFIX] Prevent number of remote write shards from going negative.
* [BUGFIX] In the graphs created by the expression browser, render very large
and small numbers in a readable way.
* [BUGFIX] Fix a rarely occurring iterator issue in varbit encoded chunks.
## v2.0.0-beta.5 / 2017-09-21 ## v2.0.0-beta.5 / 2017-09-21
This release includes numerous changes to the new storage layer. The main changes are: This release includes numerous changes to the new storage layer. The main changes are:

View file

@ -14,6 +14,7 @@
GO := GO15VENDOREXPERIMENT=1 go GO := GO15VENDOREXPERIMENT=1 go
FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH))) FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH)))
PROMU := $(FIRST_GOPATH)/bin/promu PROMU := $(FIRST_GOPATH)/bin/promu
STATICCHECK := $(FIRST_GOPATH)/bin/staticcheck
pkgs = $(shell $(GO) list ./... | grep -v /vendor/) pkgs = $(shell $(GO) list ./... | grep -v /vendor/)
PREFIX ?= $(shell pwd) PREFIX ?= $(shell pwd)
@ -25,8 +26,15 @@ ifdef DEBUG
bindata_flags = -debug bindata_flags = -debug
endif endif
STATICCHECK_IGNORE = \
github.com/prometheus/prometheus/discovery/kubernetes/node.go:SA1019 \
github.com/prometheus/prometheus/documentation/examples/remote_storage/remote_storage_adapter/main.go:SA1019 \
github.com/prometheus/prometheus/storage/local/codable/codable.go:SA6002 \
github.com/prometheus/prometheus/storage/local/persistence.go:SA6002 \
github.com/prometheus/prometheus/storage/remote/queue_manager.go:SA1015 \
github.com/prometheus/prometheus/web/web.go:SA1019
all: format build test all: format staticcheck build test
style: style:
@echo ">> checking code style" @echo ">> checking code style"
@ -53,6 +61,10 @@ vet:
@echo ">> vetting code" @echo ">> vetting code"
@$(GO) vet $(pkgs) @$(GO) vet $(pkgs)
staticcheck: $(STATICCHECK)
@echo ">> running staticcheck"
@$(STATICCHECK) -ignore "$(STATICCHECK_IGNORE)" $(pkgs)
build: promu build: promu
@echo ">> building binaries" @echo ">> building binaries"
@$(PROMU) build --prefix $(PREFIX) @$(PROMU) build --prefix $(PREFIX)
@ -77,5 +89,7 @@ promu:
GOARCH=$(subst x86_64,amd64,$(patsubst i%86,386,$(shell uname -m))) \ GOARCH=$(subst x86_64,amd64,$(patsubst i%86,386,$(shell uname -m))) \
$(GO) get -u github.com/prometheus/promu $(GO) get -u github.com/prometheus/promu
$(FIRST_GOPATH)/bin/staticcheck:
@GOOS= GOARCH= $(GO) get -u honnef.co/go/tools/cmd/staticcheck
.PHONY: all style check_license format build test vet assets tarball docker promu .PHONY: all style check_license format build test vet assets tarball docker promu staticcheck $(FIRST_GOPATH)/bin/staticcheck

View file

@ -2,7 +2,7 @@ machine:
environment: environment:
DOCKER_IMAGE_NAME: prom/prometheus DOCKER_IMAGE_NAME: prom/prometheus
QUAY_IMAGE_NAME: quay.io/prometheus/prometheus QUAY_IMAGE_NAME: quay.io/prometheus/prometheus
DOCKER_TEST_IMAGE_NAME: quay.io/prometheus/golang-builder:1.8-base DOCKER_TEST_IMAGE_NAME: quay.io/prometheus/golang-builder:1.9-base
REPO_PATH: github.com/prometheus/prometheus REPO_PATH: github.com/prometheus/prometheus
pre: pre:
- sudo curl -L -o /usr/bin/docker 'https://s3-external-1.amazonaws.com/circle-downloads/docker-1.9.1-circleci' - sudo curl -L -o /usr/bin/docker 'https://s3-external-1.amazonaws.com/circle-downloads/docker-1.9.1-circleci'

View file

@ -27,7 +27,6 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/asaskevich/govalidator"
"github.com/go-kit/kit/log" "github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level" "github.com/go-kit/kit/log/level"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -363,7 +362,7 @@ func main() {
webHandler.Ready() webHandler.Ready()
level.Info(logger).Log("msg", "Server is ready to receive requests.") level.Info(logger).Log("msg", "Server is ready to receive requests.")
term := make(chan os.Signal) term := make(chan os.Signal, 1)
signal.Notify(term, os.Interrupt, syscall.SIGTERM) signal.Notify(term, os.Interrupt, syscall.SIGTERM)
select { select {
case <-term: case <-term:
@ -413,6 +412,11 @@ func reloadConfig(filename string, logger log.Logger, rls ...Reloadable) (err er
return nil return nil
} }
func startsOrEndsWithQuote(s string) bool {
return strings.HasPrefix(s, "\"") || strings.HasPrefix(s, "'") ||
strings.HasSuffix(s, "\"") || strings.HasSuffix(s, "'")
}
// computeExternalURL computes a sanitized external URL from a raw input. It infers unset // computeExternalURL computes a sanitized external URL from a raw input. It infers unset
// URL parts from the OS and the given listen address. // URL parts from the OS and the given listen address.
func computeExternalURL(u, listenAddr string) (*url.URL, error) { func computeExternalURL(u, listenAddr string) (*url.URL, error) {
@ -428,8 +432,8 @@ func computeExternalURL(u, listenAddr string) (*url.URL, error) {
u = fmt.Sprintf("http://%s:%s/", hostname, port) u = fmt.Sprintf("http://%s:%s/", hostname, port)
} }
if ok := govalidator.IsURL(u); !ok { if startsOrEndsWithQuote(u) {
return nil, fmt.Errorf("invalid external URL %q", u) return nil, fmt.Errorf("URL must not begin or end with quotes")
} }
eu, err := url.Parse(u) eu, err := url.Parse(u)

View file

@ -17,29 +17,49 @@ import "testing"
func TestComputeExternalURL(t *testing.T) { func TestComputeExternalURL(t *testing.T) {
tests := []struct { tests := []struct {
extURL string input string
valid bool valid bool
}{ }{
{ {
extURL: "", input: "",
valid: true, valid: true,
}, },
{ {
extURL: "http://proxy.com/prometheus", input: "http://proxy.com/prometheus",
valid: true, valid: true,
}, },
{ {
extURL: "https:/proxy.com/prometheus", input: "'https://url/prometheus'",
valid: false, valid: false,
},
{
input: "'relative/path/with/quotes'",
valid: false,
},
{
input: "http://alertmanager.company.com",
valid: true,
},
{
input: "https://double--dash.de",
valid: true,
},
{
input: "'http://starts/with/quote",
valid: false,
},
{
input: "ends/with/quote\"",
valid: false,
}, },
} }
for i, test := range tests { for i, test := range tests {
r, err := computeExternalURL(test.extURL, "0.0.0.0:9090") r, err := computeExternalURL(test.input, "0.0.0.0:9090")
if test.valid && err != nil { if test.valid && err != nil {
t.Errorf("%d. expected input to be valid, got %s", i, err) t.Errorf("%d. expected input to be valid, got %s", i, err)
} else if !test.valid && err == nil { } else if !test.valid && err == nil {
t.Logf("%+v", r) t.Logf("%+v", test, r)
t.Errorf("%d. expected input to be invalid", i) t.Errorf("%d. expected input to be invalid", i)
} }
} }

View file

@ -468,10 +468,7 @@ func (c *TLSConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
if err := unmarshal((*plain)(c)); err != nil { if err := unmarshal((*plain)(c)); err != nil {
return err return err
} }
if err := checkOverflow(c.XXX, "TLS config"); err != nil { return checkOverflow(c.XXX, "TLS config")
return err
}
return nil
} }
// ServiceDiscoveryConfig configures lists of different service discovery mechanisms. // ServiceDiscoveryConfig configures lists of different service discovery mechanisms.
@ -513,10 +510,7 @@ func (c *ServiceDiscoveryConfig) UnmarshalYAML(unmarshal func(interface{}) error
if err := unmarshal((*plain)(c)); err != nil { if err := unmarshal((*plain)(c)); err != nil {
return err return err
} }
if err := checkOverflow(c.XXX, "service discovery config"); err != nil { return checkOverflow(c.XXX, "service discovery config")
return err
}
return nil
} }
// HTTPClientConfig configures an HTTP client. // HTTPClientConfig configures an HTTP client.
@ -633,10 +627,7 @@ func (c *AlertingConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
if err := unmarshal((*plain)(c)); err != nil { if err := unmarshal((*plain)(c)); err != nil {
return err return err
} }
if err := checkOverflow(c.XXX, "alerting config"); err != nil { return checkOverflow(c.XXX, "alerting config")
return err
}
return nil
} }
// AlertmanagerConfig configures how Alertmanagers can be discovered and communicated with. // AlertmanagerConfig configures how Alertmanagers can be discovered and communicated with.
@ -1088,10 +1079,7 @@ func (c *KubernetesNamespaceDiscovery) UnmarshalYAML(unmarshal func(interface{})
if err != nil { if err != nil {
return err return err
} }
if err := checkOverflow(c.XXX, "namespaces"); err != nil { return checkOverflow(c.XXX, "namespaces")
return err
}
return nil
} }
// GCESDConfig is the configuration for GCE based service discovery. // GCESDConfig is the configuration for GCE based service discovery.
@ -1472,10 +1460,7 @@ func (c *RemoteWriteConfig) UnmarshalYAML(unmarshal func(interface{}) error) err
return err return err
} }
if err := checkOverflow(c.XXX, "remote_write"); err != nil { return checkOverflow(c.XXX, "remote_write")
return err
}
return nil
} }
// QueueConfig is the configuration for the queue used to write to remote // QueueConfig is the configuration for the queue used to write to remote
@ -1532,8 +1517,5 @@ func (c *RemoteReadConfig) UnmarshalYAML(unmarshal func(interface{}) error) erro
return err return err
} }
if err := checkOverflow(c.XXX, "remote_read"); err != nil { return checkOverflow(c.XXX, "remote_read")
return err
}
return nil
} }

View file

@ -129,7 +129,7 @@ func (d *Discovery) refreshAll(ctx context.Context, ch chan<- []*config.TargetGr
} }
func (d *Discovery) refresh(ctx context.Context, name string, ch chan<- []*config.TargetGroup) error { func (d *Discovery) refresh(ctx context.Context, name string, ch chan<- []*config.TargetGroup) error {
response, err := lookupAll(name, d.qtype, d.logger) response, err := lookupWithSearchPath(name, d.qtype, d.logger)
dnsSDLookupsCount.Inc() dnsSDLookupsCount.Inc()
if err != nil { if err != nil {
dnsSDLookupFailuresCount.Inc() dnsSDLookupFailuresCount.Inc()
@ -156,7 +156,6 @@ func (d *Discovery) refresh(ctx context.Context, name string, ch chan<- []*confi
default: default:
level.Warn(d.logger).Log("msg", "Invalid SRV record", "record", record) level.Warn(d.logger).Log("msg", "Invalid SRV record", "record", record)
continue continue
} }
tg.Targets = append(tg.Targets, model.LabelSet{ tg.Targets = append(tg.Targets, model.LabelSet{
model.AddressLabel: target, model.AddressLabel: target,
@ -174,35 +173,109 @@ func (d *Discovery) refresh(ctx context.Context, name string, ch chan<- []*confi
return nil return nil
} }
func lookupAll(name string, qtype uint16, logger log.Logger) (*dns.Msg, error) { // lookupWithSearchPath tries to get an answer for various permutations of
// the given name, appending the system-configured search path as necessary.
//
// There are three possible outcomes:
//
// 1. One of the permutations of the given name is recognised as
// "valid" by the DNS, in which case we consider ourselves "done"
// and that answer is returned. Note that, due to the way the DNS
// handles "name has resource records, but none of the specified type",
// the answer received may have an empty set of results.
//
// 2. All of the permutations of the given name are responded to by one of
// the servers in the "nameservers" list with the answer "that name does
// not exist" (NXDOMAIN). In that case, it can be considered
// pseudo-authoritative that there are no records for that name.
//
// 3. One or more of the names was responded to by all servers with some
// sort of error indication. In that case, we can't know if, in fact,
// there are records for the name or not, so whatever state the
// configuration is in, we should keep it that way until we know for
// sure (by, presumably, all the names getting answers in the future).
//
// Outcomes 1 and 2 are indicated by a valid response message (possibly an
// empty one) and no error. Outcome 3 is indicated by an error return. The
// error will be generic-looking, because trying to return all the errors
// returned by the combination of all name permutations and servers is a
// nightmare.
func lookupWithSearchPath(name string, qtype uint16, logger log.Logger) (*dns.Msg, error) {
conf, err := dns.ClientConfigFromFile(resolvConf) conf, err := dns.ClientConfigFromFile(resolvConf)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not load resolv.conf: %s", err) return nil, fmt.Errorf("could not load resolv.conf: %s", err)
} }
allResponsesValid := true
for _, lname := range conf.NameList(name) {
response, err := lookupFromAnyServer(lname, qtype, conf, logger)
if err != nil {
// We can't go home yet, because a later name
// may give us a valid, successful answer. However
// we can no longer say "this name definitely doesn't
// exist", because we did not get that answer for
// at least one name.
allResponsesValid = false
} else if response.Rcode == dns.RcodeSuccess {
// Outcome 1: GOLD!
return response, nil
}
}
if allResponsesValid {
// Outcome 2: everyone says NXDOMAIN, that's good enough for me
return &dns.Msg{}, nil
}
// Outcome 3: boned.
return nil, fmt.Errorf("could not resolve %q: all servers responded with errors to at least one search domain", name)
}
// lookupFromAnyServer uses all configured servers to try and resolve a specific
// name. If a viable answer is received from a server, then it is
// immediately returned, otherwise the other servers in the config are
// tried, and if none of them return a viable answer, an error is returned.
//
// A "viable answer" is one which indicates either:
//
// 1. "yes, I know that name, and here are its records of the requested type"
// (RCODE==SUCCESS, ANCOUNT > 0);
// 2. "yes, I know that name, but it has no records of the requested type"
// (RCODE==SUCCESS, ANCOUNT==0); or
// 3. "I know that name doesn't exist" (RCODE==NXDOMAIN).
//
// A non-viable answer is "anything else", which encompasses both various
// system-level problems (like network timeouts) and also
// valid-but-unexpected DNS responses (SERVFAIL, REFUSED, etc).
func lookupFromAnyServer(name string, qtype uint16, conf *dns.ClientConfig, logger log.Logger) (*dns.Msg, error) {
client := &dns.Client{} client := &dns.Client{}
response := &dns.Msg{}
for _, server := range conf.Servers { for _, server := range conf.Servers {
servAddr := net.JoinHostPort(server, conf.Port) servAddr := net.JoinHostPort(server, conf.Port)
for _, lname := range conf.NameList(name) { msg, err := askServerForName(name, qtype, client, servAddr, false)
response, err = lookup(lname, qtype, client, servAddr, false) if err != nil {
if err != nil { level.Warn(logger).Log("msg", "DNS resolution failed", "server", server, "name", name, "err", err)
level.Warn(logger).Log("msg", "DNS resolution failed", "server", server, "name", name, "err", err) continue
continue }
}
if len(response.Answer) > 0 { if msg.Rcode == dns.RcodeSuccess || msg.Rcode == dns.RcodeNameError {
return response, nil // We have our answer. Time to go home.
} return msg, nil
} }
} }
return response, fmt.Errorf("could not resolve %s: no server responded", name)
return nil, fmt.Errorf("could not resolve %s: no servers returned a viable answer", name)
} }
func lookup(lname string, queryType uint16, client *dns.Client, servAddr string, edns bool) (*dns.Msg, error) { // askServerForName makes a request to a specific DNS server for a specific
// name (and qtype). Retries in the event of response truncation, but
// otherwise just sends back whatever the server gave, whether that be a
// valid-looking response, or an error.
func askServerForName(name string, queryType uint16, client *dns.Client, servAddr string, edns bool) (*dns.Msg, error) {
msg := &dns.Msg{} msg := &dns.Msg{}
msg.SetQuestion(dns.Fqdn(lname), queryType)
msg.SetQuestion(dns.Fqdn(name), queryType)
if edns { if edns {
msg.SetEdns0(dns.DefaultMsgSize, false) msg.SetEdns0(dns.DefaultMsgSize, false)
} }
@ -215,7 +288,7 @@ func lookup(lname string, queryType uint16, client *dns.Client, servAddr string,
if edns { // Truncated even though EDNS is used if edns { // Truncated even though EDNS is used
client.Net = "tcp" client.Net = "tcp"
} }
return lookup(lname, queryType, client, servAddr, !edns) return askServerForName(name, queryType, client, servAddr, !edns)
} }
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -345,8 +345,8 @@ func (r *AlertingRule) HTMLSnippet(pathPrefix string) html_template.HTML {
} }
ar := rulefmt.Rule{ ar := rulefmt.Rule{
Alert: fmt.Sprintf("<a href=%q>%s</a>", pathPrefix+strutil.GraphLinkForExpression(alertMetric.String()), r.name), Alert: fmt.Sprintf("<a href=%q>%s</a>", pathPrefix+strutil.TableLinkForExpression(alertMetric.String()), r.name),
Expr: fmt.Sprintf("<a href=%q>%s</a>", pathPrefix+strutil.GraphLinkForExpression(r.vector.String()), html_template.HTMLEscapeString(r.vector.String())), Expr: fmt.Sprintf("<a href=%q>%s</a>", pathPrefix+strutil.TableLinkForExpression(r.vector.String()), html_template.HTMLEscapeString(r.vector.String())),
For: model.Duration(r.holdDuration), For: model.Duration(r.holdDuration),
Labels: labels, Labels: labels,
Annotations: annotations, Annotations: annotations,

View file

@ -27,8 +27,8 @@ func TestAlertingRuleHTMLSnippet(t *testing.T) {
} }
rule := NewAlertingRule("testrule", expr, 0, labels.FromStrings("html", "<b>BOLD</b>"), labels.FromStrings("html", "<b>BOLD</b>"), nil) rule := NewAlertingRule("testrule", expr, 0, labels.FromStrings("html", "<b>BOLD</b>"), labels.FromStrings("html", "<b>BOLD</b>"), nil)
const want = `alert: <a href="/test/prefix/graph?g0.expr=ALERTS%7Balertname%3D%22testrule%22%7D&g0.tab=0">testrule</a> const want = `alert: <a href="/test/prefix/graph?g0.expr=ALERTS%7Balertname%3D%22testrule%22%7D&g0.tab=1">testrule</a>
expr: <a href="/test/prefix/graph?g0.expr=foo%7Bhtml%3D%22%3Cb%3EBOLD%3Cb%3E%22%7D&g0.tab=0">foo{html=&#34;&lt;b&gt;BOLD&lt;b&gt;&#34;}</a> expr: <a href="/test/prefix/graph?g0.expr=foo%7Bhtml%3D%22%3Cb%3EBOLD%3Cb%3E%22%7D&g0.tab=1">foo{html=&#34;&lt;b&gt;BOLD&lt;b&gt;&#34;}</a>
labels: labels:
html: '&lt;b&gt;BOLD&lt;/b&gt;' html: '&lt;b&gt;BOLD&lt;/b&gt;'
annotations: annotations:

View file

@ -390,7 +390,7 @@ func (g *Group) sendAlerts(rule *AlertingRule) error {
StartsAt: alert.ActiveAt.Add(rule.holdDuration), StartsAt: alert.ActiveAt.Add(rule.holdDuration),
Labels: alert.Labels, Labels: alert.Labels,
Annotations: alert.Annotations, Annotations: alert.Annotations,
GeneratorURL: g.opts.ExternalURL.String() + strutil.GraphLinkForExpression(rule.vector.String()), GeneratorURL: g.opts.ExternalURL.String() + strutil.TableLinkForExpression(rule.vector.String()),
} }
if !alert.ResolvedAt.IsZero() { if !alert.ResolvedAt.IsZero() {
a.EndsAt = alert.ResolvedAt a.EndsAt = alert.ResolvedAt

View file

@ -123,8 +123,8 @@ func (rule RecordingRule) HTMLSnippet(pathPrefix string) template.HTML {
} }
r := rulefmt.Rule{ r := rulefmt.Rule{
Record: fmt.Sprintf(`<a href="%s">%s</a>`, pathPrefix+strutil.GraphLinkForExpression(rule.name), rule.name), Record: fmt.Sprintf(`<a href="%s">%s</a>`, pathPrefix+strutil.TableLinkForExpression(rule.name), rule.name),
Expr: fmt.Sprintf(`<a href="%s">%s</a>`, pathPrefix+strutil.GraphLinkForExpression(ruleExpr), template.HTMLEscapeString(ruleExpr)), Expr: fmt.Sprintf(`<a href="%s">%s</a>`, pathPrefix+strutil.TableLinkForExpression(ruleExpr), template.HTMLEscapeString(ruleExpr)),
Labels: labels, Labels: labels,
} }

View file

@ -81,8 +81,8 @@ func TestRecordingRuleHTMLSnippet(t *testing.T) {
} }
rule := NewRecordingRule("testrule", expr, labels.FromStrings("html", "<b>BOLD</b>")) rule := NewRecordingRule("testrule", expr, labels.FromStrings("html", "<b>BOLD</b>"))
const want = `record: <a href="/test/prefix/graph?g0.expr=testrule&g0.tab=0">testrule</a> const want = `record: <a href="/test/prefix/graph?g0.expr=testrule&g0.tab=1">testrule</a>
expr: <a href="/test/prefix/graph?g0.expr=foo%7Bhtml%3D%22%3Cb%3EBOLD%3Cb%3E%22%7D&g0.tab=0">foo{html=&#34;&lt;b&gt;BOLD&lt;b&gt;&#34;}</a> expr: <a href="/test/prefix/graph?g0.expr=foo%7Bhtml%3D%22%3Cb%3EBOLD%3Cb%3E%22%7D&g0.tab=1">foo{html=&#34;&lt;b&gt;BOLD&lt;b&gt;&#34;}</a>
labels: labels:
html: '&lt;b&gt;BOLD&lt;/b&gt;' html: '&lt;b&gt;BOLD&lt;/b&gt;'
` `

View file

@ -41,7 +41,7 @@ func NewClientFromConfig(cfg config.HTTPClientConfig) (*http.Client, error) {
// It is applied on request. So we leave out any timings here. // It is applied on request. So we leave out any timings here.
var rt http.RoundTripper = &http.Transport{ var rt http.RoundTripper = &http.Transport{
Proxy: http.ProxyURL(cfg.ProxyURL.URL), Proxy: http.ProxyURL(cfg.ProxyURL.URL),
MaxIdleConns: 10000, MaxIdleConns: 20000,
DisableKeepAlives: false, DisableKeepAlives: false,
TLSClientConfig: tlsConfig, TLSClientConfig: tlsConfig,
DisableCompression: true, DisableCompression: true,

View file

@ -53,7 +53,6 @@ func (t *Timer) String() string {
// A TimerGroup represents a group of timers relevant to a single query. // A TimerGroup represents a group of timers relevant to a single query.
type TimerGroup struct { type TimerGroup struct {
timers map[fmt.Stringer]*Timer timers map[fmt.Stringer]*Timer
child *TimerGroup
} }
// NewTimerGroup constructs a new TimerGroup. // NewTimerGroup constructs a new TimerGroup.

View file

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

View file

@ -1,421 +0,0 @@
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) [![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).
#### Installation
Make sure that Go is installed on your computer.
Type the following command in your terminal:
go get github.com/asaskevich/govalidator
or you can get specified release of the package with `gopkg.in`:
go get gopkg.in/asaskevich/govalidator.v4
After it the package is ready to use.
#### Import package in your project
Add following line in your `*.go` file:
```go
import "github.com/asaskevich/govalidator"
```
If you are unhappy to use long `govalidator`, you can do something like this:
```go
import (
valid "github.com/asaskevich/govalidator"
)
```
#### Activate behavior to require all fields have a validation tag by default
`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"`). A good place to activate this is a package init function or the main() function.
```go
import "github.com/asaskevich/govalidator"
func init() {
govalidator.SetFieldsRequiredByDefault(true)
}
```
Here's some code to explain it:
```go
// this struct definition will fail govalidator.ValidateStruct() (and the field values do not matter):
type exampleStruct struct {
Name string ``
Email string `valid:"email"`
}
// this, however, will only fail when Email is empty or an invalid email address:
type exampleStruct2 struct {
Name string `valid:"-"`
Email string `valid:"email"`
}
// lastly, this will only fail when Email is an invalid email address but not when it's empty:
type exampleStruct2 struct {
Name string `valid:"-"`
Email string `valid:"email,optional"`
}
```
#### Recent breaking changes (see [#123](https://github.com/asaskevich/govalidator/pull/123))
##### Custom validator function signature
A context was added as the second parameter, for structs this is the object being validated this makes dependent validation possible.
```go
import "github.com/asaskevich/govalidator"
// old signature
func(i interface{}) bool
// new signature
func(i interface{}, o interface{}) bool
```
##### Adding a custom validator
This was changed to prevent data races when accessing custom validators.
```go
import "github.com/asaskevich/govalidator"
// before
govalidator.CustomTypeTagMap["customByteArrayValidator"] = CustomTypeValidator(func(i interface{}, o interface{}) bool {
// ...
})
// after
govalidator.CustomTypeTagMap.Set("customByteArrayValidator", CustomTypeValidator(func(i interface{}, o interface{}) bool {
// ...
}))
```
#### List of functions:
```go
func Abs(value float64) float64
func BlackList(str, chars string) string
func ByteLength(str 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 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 IsDivisibleBy(str, num string) bool
func IsEmail(str string) bool
func IsFilePath(str string) (bool, int)
func IsFloat(str string) bool
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
func IsISBN(str string, version int) bool
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
func IsLongitude(str string) bool
func IsLowerCase(str string) bool
func IsMAC(str string) bool
func IsMongoID(str string) bool
func IsMultibyte(str string) bool
func IsNatural(value float64) bool
func IsNegative(value float64) bool
func IsNonNegative(value float64) bool
func IsNonPositive(value float64) bool
func IsNull(str string) bool
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
func IsUTFLetterNumeric(str string) bool
func IsUTFNumeric(str string) bool
func IsUUID(str string) bool
func IsUUIDv3(str string) bool
func IsUUIDv4(str string) bool
func IsUUIDv5(str string) bool
func IsUpperCase(str string) bool
func IsVariableWidth(str string) bool
func IsWhole(value float64) bool
func LeftTrim(str, chars string) string
func Map(array []interface{}, iterator ResultIterator) []interface{}
func Matches(str, pattern string) bool
func 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)
func ToInt(str string) (int64, error)
func ToJSON(obj interface{}) (string, error)
func ToString(obj interface{}) string
func Trim(str, chars string) string
func Truncate(str string, length int, ending string) string
func 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
type ResultIterator
type UnsupportedTypeError
func (e *UnsupportedTypeError) Error() string
type Validator
```
#### Examples
###### IsURL
```go
println(govalidator.IsURL(`http://user@pass:domain.com/path/page`))
```
###### ToString
```go
type User struct {
FirstName string
LastName string
}
str := govalidator.ToString(&User{"John", "Juan"})
println(str)
```
###### Each, Map, Filter, Count for slices
Each iterates over the slice/array and calls Iterator for every item
```go
data := []interface{}{1, 2, 3, 4, 5}
var fn govalidator.Iterator = func(value interface{}, index int) {
println(value.(int))
}
govalidator.Each(data, fn)
```
```go
data := []interface{}{1, 2, 3, 4, 5}
var fn govalidator.ResultIterator = func(value interface{}, index int) interface{} {
return value.(int) * 3
}
_ = govalidator.Map(data, fn) // result = []interface{}{1, 6, 9, 12, 15}
```
```go
data := []interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var fn govalidator.ConditionIterator = func(value interface{}, index int) bool {
return value.(int)%2 == 0
}
_ = govalidator.Filter(data, fn) // result = []interface{}{2, 4, 6, 8, 10}
_ = govalidator.Count(data, fn) // result = 5
```
###### ValidateStruct [#2](https://github.com/asaskevich/govalidator/pull/2)
If you want to validate structs, you can use tag `valid` for any field in your structure. All validators used with this field in one tag are separated by comma. If you want to skip validation, place `-` in your tag. If you need a validator that is not on the list below, you can add it like this:
```go
govalidator.TagMap["duck"] = govalidator.Validator(func(str string) bool {
return str == "duck"
})
```
For completely custom validators (interface-based), see below.
Here is a list of available validators for struct fields (validator - used function):
```go
"email": IsEmail,
"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,
"rgbcolor": IsRGBcolor,
"lowercase": IsLowerCase,
"uppercase": IsUpperCase,
"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:
```go
type Post struct {
Title string `valid:"alphanum,required"`
Message string `valid:"duck,ascii"`
AuthorIP string `valid:"ipv4"`
Date string `valid:"-"`
}
post := &Post{
Title: "My Example Post",
Message: "duck",
AuthorIP: "123.234.54.3",
}
// Add your own struct validation tags
govalidator.TagMap["duck"] = govalidator.Validator(func(str string) bool {
return str == "duck"
})
result, err := govalidator.ValidateStruct(post)
if err != nil {
println("error: " + err.Error())
}
println(result)
```
###### WhiteList
```go
// Remove all characters from string ignoring characters between "a" and "z"
println(govalidator.WhiteList("a3a43a5a4a3a2a23a4a5a4a3a4", "a-z") == "aaaaaaaaaaaa")
```
###### Custom validation functions
Custom validation using your own domain specific validators is also available - here's an example of how to use it:
```go
import "github.com/asaskevich/govalidator"
type CustomByteArray [6]byte // custom types are supported and can be validated
type StructWithCustomByteArray struct {
ID CustomByteArray `valid:"customByteArrayValidator,customMinLengthValidator"` // multiple custom validators are possible as well and will be evaluated in sequence
Email string `valid:"email"`
CustomMinLength int `valid:"-"`
}
govalidator.CustomTypeTagMap.Set("customByteArrayValidator", CustomTypeValidator(func(i interface{}, context interface{}) bool {
switch v := context.(type) { // you can type switch on the context interface being validated
case StructWithCustomByteArray:
// you can check and validate against some other field in the context,
// return early or not validate against the context at all your choice
case SomeOtherType:
// ...
default:
// expecting some other type? Throw/panic here or continue
}
switch v := i.(type) { // type switch on the struct field being validated
case CustomByteArray:
for _, e := range v { // this validator checks that the byte array is not empty, i.e. not all zeroes
if e != 0 {
return true
}
}
}
return false
}))
govalidator.CustomTypeTagMap.Set("customMinLengthValidator", CustomTypeValidator(func(i interface{}, context interface{}) bool {
switch v := context.(type) { // this validates a field against the value in another field, i.e. dependent validation
case StructWithCustomByteArray:
return len(v.ID) >= v.CustomMinLength
}
return false
}))
```
#### Notes
Documentation is available here: [godoc.org](https://godoc.org/github.com/asaskevich/govalidator).
Full information about code coverage is also available here: [govalidator on gocover.io](http://gocover.io/github.com/asaskevich/govalidator).
#### Support
If you do have a contribution for the package feel free to put up a Pull Request or open Issue.
#### Special thanks to [contributors](https://github.com/asaskevich/govalidator/graphs/contributors)
* [Daniel Lohse](https://github.com/annismckenzie)
* [Attila Oláh](https://github.com/attilaolah)
* [Daniel Korner](https://github.com/Dadie)
* [Steven Wilkin](https://github.com/stevenwilkin)
* [Deiwin Sarjas](https://github.com/deiwin)
* [Noah Shibley](https://github.com/slugmobile)
* [Nathan Davies](https://github.com/nathj07)
* [Matt Sanford](https://github.com/mzsanford)
* [Simon ccl1115](https://github.com/ccl1115)

View file

@ -1,58 +0,0 @@
package govalidator
// Iterator is the function that accepts element of slice/array and its index
type Iterator func(interface{}, int)
// ResultIterator is the function that accepts element of slice/array and its index and returns any result
type ResultIterator func(interface{}, int) interface{}
// ConditionIterator is the function that accepts element of slice/array and its index and returns boolean
type ConditionIterator func(interface{}, int) bool
// Each iterates over the slice and apply Iterator to every item
func Each(array []interface{}, iterator Iterator) {
for index, data := range array {
iterator(data, index)
}
}
// Map iterates over the slice and apply ResultIterator to every item. Returns new slice as a result.
func Map(array []interface{}, iterator ResultIterator) []interface{} {
var result = make([]interface{}, len(array))
for index, data := range array {
result[index] = iterator(data, index)
}
return result
}
// Find iterates over the slice and apply ConditionIterator to every item. Returns first item that meet ConditionIterator or nil otherwise.
func Find(array []interface{}, iterator ConditionIterator) interface{} {
for index, data := range array {
if iterator(data, index) {
return data
}
}
return nil
}
// Filter iterates over the slice and apply ConditionIterator to every item. Returns new slice.
func Filter(array []interface{}, iterator ConditionIterator) []interface{} {
var result = make([]interface{}, 0)
for index, data := range array {
if iterator(data, index) {
result = append(result, data)
}
}
return result
}
// Count iterates over the slice and apply ConditionIterator to every item. Returns count of items that meets ConditionIterator.
func Count(array []interface{}, iterator ConditionIterator) int {
count := 0
for index, data := range array {
if iterator(data, index) {
count = count + 1
}
}
return count
}

View file

@ -1,49 +0,0 @@
package govalidator
import (
"encoding/json"
"fmt"
"strconv"
)
// ToString convert the input to a string.
func ToString(obj interface{}) string {
res := fmt.Sprintf("%v", obj)
return string(res)
}
// ToJSON convert the input to a valid JSON string
func ToJSON(obj interface{}) (string, error) {
res, err := json.Marshal(obj)
if err != nil {
res = []byte("")
}
return string(res), err
}
// ToFloat convert the input string to a float, or 0.0 if the input is not a float.
func ToFloat(str string) (float64, error) {
res, err := strconv.ParseFloat(str, 64)
if err != nil {
res = 0.0
}
return res, err
}
// ToInt convert the input string to an integer, or 0 if the input is not an integer.
func ToInt(str string) (int64, error) {
res, err := strconv.ParseInt(str, 0, 64)
if err != nil {
res = 0
}
return res, err
}
// ToBoolean convert the input string to a boolean.
func ToBoolean(str string) (bool, error) {
res, err := strconv.ParseBool(str)
if err != nil {
res = false
}
return res, err
}

View file

@ -1,31 +0,0 @@
package govalidator
// Errors is an array of multiple errors and conforms to the error interface.
type Errors []error
// Errors returns itself.
func (es Errors) Errors() []error {
return es
}
func (es Errors) Error() string {
var err string
for _, e := range es {
err += e.Error() + ";"
}
return err
}
// Error encapsulates a name, an error and whether there's a custom error message or not.
type Error struct {
Name string
Err error
CustomErrorMessageExists bool
}
func (e Error) Error() string {
if e.CustomErrorMessageExists {
return e.Err.Error()
}
return e.Name + ": " + e.Err.Error()
}

View file

@ -1,57 +0,0 @@
package govalidator
import "math"
// Abs returns absolute value of number
func Abs(value float64) float64 {
return value * Sign(value)
}
// Sign returns signum of number: 1 in case of value > 0, -1 in case of value < 0, 0 otherwise
func Sign(value float64) float64 {
if value > 0 {
return 1
} else if value < 0 {
return -1
} else {
return 0
}
}
// IsNegative returns true if value < 0
func IsNegative(value float64) bool {
return value < 0
}
// IsPositive returns true if value > 0
func IsPositive(value float64) bool {
return value > 0
}
// IsNonNegative returns true if value >= 0
func IsNonNegative(value float64) bool {
return value >= 0
}
// IsNonPositive returns true if value <= 0
func IsNonPositive(value float64) bool {
return value <= 0
}
// InRange returns true if value lies between left and right border
func InRange(value, left, right float64) bool {
if left > right {
left, right = right, left
}
return value >= left && value <= right
}
// IsWhole returns true if value is whole number
func IsWhole(value float64) bool {
return Abs(math.Remainder(value, 1)) == 0
}
// IsNatural returns true if value is natural number (positive and whole)
func IsNatural(value float64) bool {
return IsWhole(value) && IsPositive(value)
}

View file

@ -1,91 +0,0 @@
package govalidator
import "regexp"
// Basic regular expressions for validating strings
const (
Email string = "^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
CreditCard string = "^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$"
ISBN10 string = "^(?:[0-9]{9}X|[0-9]{10})$"
ISBN13 string = "^(?:[0-9]{13})$"
UUID3 string = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$"
UUID4 string = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
UUID5 string = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
UUID string = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
Alpha string = "^[a-zA-Z]+$"
Alphanumeric string = "^[a-zA-Z0-9]+$"
Numeric string = "^[0-9]+$"
Int string = "^(?:[-+]?(?:0|[1-9][0-9]*))$"
Float string = "^(?:[-+]?(?:[0-9]+))?(?:\\.[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$"
Hexadecimal string = "^[0-9a-fA-F]+$"
Hexcolor string = "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
RGBcolor string = "^rgb\\(\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*\\)$"
ASCII string = "^[\x00-\x7F]+$"
Multibyte string = "[^\x00-\x7F]"
FullWidth string = "[^\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]"
HalfWidth string = "[\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]"
Base64 string = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
PrintableASCII string = "^[\x20-\x7E]+$"
DataURI string = "^data:.+\\/(.+);base64$"
Latitude string = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"
Longitude string = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
DNSName string = `^([a-zA-Z0-9]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9]{1}[a-zA-Z0-9_-]{0,62})*$`
IP string = `(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))`
URLSchema string = `((ftp|tcp|udp|wss?|https?):\/\/)`
URLUsername string = `(\S+(:\S*)?@)`
Hostname string = ``
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 + `?$`
SSN string = `^\d{3}[- ]?\d{2}[- ]?\d{4}$`
WinPath string = `^[a-zA-Z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$`
UnixPath string = `^(/[^/\x00]*)+/?$`
Semver string = "^v?(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(-(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\\+[0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)?$"
tagName string = "valid"
)
// Used by IsFilePath func
const (
// Unknown is unresolved OS type
Unknown = iota
// Win is Windows type
Win
// Unix is *nix OS types
Unix
)
var (
rxEmail = regexp.MustCompile(Email)
rxCreditCard = regexp.MustCompile(CreditCard)
rxISBN10 = regexp.MustCompile(ISBN10)
rxISBN13 = regexp.MustCompile(ISBN13)
rxUUID3 = regexp.MustCompile(UUID3)
rxUUID4 = regexp.MustCompile(UUID4)
rxUUID5 = regexp.MustCompile(UUID5)
rxUUID = regexp.MustCompile(UUID)
rxAlpha = regexp.MustCompile(Alpha)
rxAlphanumeric = regexp.MustCompile(Alphanumeric)
rxNumeric = regexp.MustCompile(Numeric)
rxInt = regexp.MustCompile(Int)
rxFloat = regexp.MustCompile(Float)
rxHexadecimal = regexp.MustCompile(Hexadecimal)
rxHexcolor = regexp.MustCompile(Hexcolor)
rxRGBcolor = regexp.MustCompile(RGBcolor)
rxASCII = regexp.MustCompile(ASCII)
rxPrintableASCII = regexp.MustCompile(PrintableASCII)
rxMultibyte = regexp.MustCompile(Multibyte)
rxFullWidth = regexp.MustCompile(FullWidth)
rxHalfWidth = regexp.MustCompile(HalfWidth)
rxBase64 = regexp.MustCompile(Base64)
rxDataURI = regexp.MustCompile(DataURI)
rxLatitude = regexp.MustCompile(Latitude)
rxLongitude = regexp.MustCompile(Longitude)
rxDNSName = regexp.MustCompile(DNSName)
rxURL = regexp.MustCompile(URL)
rxSSN = regexp.MustCompile(SSN)
rxWinPath = regexp.MustCompile(WinPath)
rxUnixPath = regexp.MustCompile(UnixPath)
rxSemver = regexp.MustCompile(Semver)
)

View file

@ -1,418 +0,0 @@
package govalidator
import (
"reflect"
"regexp"
"sync"
)
// Validator is a wrapper for a validator function that returns bool and accepts string.
type Validator func(str string) bool
// CustomTypeValidator is a wrapper for validator functions that returns bool and accepts any type.
// The second parameter should be the context (in the case of validating a struct: the whole object being validated).
type CustomTypeValidator func(i interface{}, o interface{}) bool
// ParamValidator is a wrapper for validator functions that accepts additional parameters.
type ParamValidator func(str string, params ...string) bool
type tagOptionsMap map[string]string
// UnsupportedTypeError is a wrapper for reflect.Type
type UnsupportedTypeError struct {
Type reflect.Type
}
// stringValues is a slice of reflect.Value holding *reflect.StringValue.
// It implements the methods to sort by string.
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+)\\)$"),
"in": regexp.MustCompile(`^in\((.*)\)`),
"matches": regexp.MustCompile(`^matches\((.+)\)$`),
}
type customTypeTagMap struct {
validators map[string]CustomTypeValidator
sync.RWMutex
}
func (tm *customTypeTagMap) Get(name string) (CustomTypeValidator, bool) {
tm.RLock()
defer tm.RUnlock()
v, ok := tm.validators[name]
return v, ok
}
func (tm *customTypeTagMap) Set(name string, ctv CustomTypeValidator) {
tm.Lock()
defer tm.Unlock()
tm.validators[name] = ctv
}
// CustomTypeTagMap is a map of functions that can be used as tags for ValidateStruct function.
// Use this to validate compound or custom types that need to be handled as a whole, e.g.
// `type UUID [16]byte` (this would be handled as an array of bytes).
var CustomTypeTagMap = &customTypeTagMap{validators: make(map[string]CustomTypeValidator)}
// TagMap is a map of functions, that can be used as tags for ValidateStruct function.
var TagMap = map[string]Validator{
"email": IsEmail,
"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,
"rgbcolor": IsRGBcolor,
"lowercase": IsLowerCase,
"uppercase": IsUpperCase,
"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,
"ISO4217": IsISO4217,
}
// ISO3166Entry stores country codes
type ISO3166Entry struct {
EnglishShortName string
FrenchShortName string
Alpha2Code string
Alpha3Code string
Numeric string
}
//ISO3166List based on https://www.iso.org/obp/ui/#search/code/ Code Type "Officially Assigned Codes"
var ISO3166List = []ISO3166Entry{
{"Afghanistan", "Afghanistan (l')", "AF", "AFG", "004"},
{"Albania", "Albanie (l')", "AL", "ALB", "008"},
{"Antarctica", "Antarctique (l')", "AQ", "ATA", "010"},
{"Algeria", "Algérie (l')", "DZ", "DZA", "012"},
{"American Samoa", "Samoa américaines (les)", "AS", "ASM", "016"},
{"Andorra", "Andorre (l')", "AD", "AND", "020"},
{"Angola", "Angola (l')", "AO", "AGO", "024"},
{"Antigua and Barbuda", "Antigua-et-Barbuda", "AG", "ATG", "028"},
{"Azerbaijan", "Azerbaïdjan (l')", "AZ", "AZE", "031"},
{"Argentina", "Argentine (l')", "AR", "ARG", "032"},
{"Australia", "Australie (l')", "AU", "AUS", "036"},
{"Austria", "Autriche (l')", "AT", "AUT", "040"},
{"Bahamas (the)", "Bahamas (les)", "BS", "BHS", "044"},
{"Bahrain", "Bahreïn", "BH", "BHR", "048"},
{"Bangladesh", "Bangladesh (le)", "BD", "BGD", "050"},
{"Armenia", "Arménie (l')", "AM", "ARM", "051"},
{"Barbados", "Barbade (la)", "BB", "BRB", "052"},
{"Belgium", "Belgique (la)", "BE", "BEL", "056"},
{"Bermuda", "Bermudes (les)", "BM", "BMU", "060"},
{"Bhutan", "Bhoutan (le)", "BT", "BTN", "064"},
{"Bolivia (Plurinational State of)", "Bolivie (État plurinational de)", "BO", "BOL", "068"},
{"Bosnia and Herzegovina", "Bosnie-Herzégovine (la)", "BA", "BIH", "070"},
{"Botswana", "Botswana (le)", "BW", "BWA", "072"},
{"Bouvet Island", "Bouvet (l'Île)", "BV", "BVT", "074"},
{"Brazil", "Brésil (le)", "BR", "BRA", "076"},
{"Belize", "Belize (le)", "BZ", "BLZ", "084"},
{"British Indian Ocean Territory (the)", "Indien (le Territoire britannique de l'océan)", "IO", "IOT", "086"},
{"Solomon Islands", "Salomon (Îles)", "SB", "SLB", "090"},
{"Virgin Islands (British)", "Vierges britanniques (les Îles)", "VG", "VGB", "092"},
{"Brunei Darussalam", "Brunéi Darussalam (le)", "BN", "BRN", "096"},
{"Bulgaria", "Bulgarie (la)", "BG", "BGR", "100"},
{"Myanmar", "Myanmar (le)", "MM", "MMR", "104"},
{"Burundi", "Burundi (le)", "BI", "BDI", "108"},
{"Belarus", "Bélarus (le)", "BY", "BLR", "112"},
{"Cambodia", "Cambodge (le)", "KH", "KHM", "116"},
{"Cameroon", "Cameroun (le)", "CM", "CMR", "120"},
{"Canada", "Canada (le)", "CA", "CAN", "124"},
{"Cabo Verde", "Cabo Verde", "CV", "CPV", "132"},
{"Cayman Islands (the)", "Caïmans (les Îles)", "KY", "CYM", "136"},
{"Central African Republic (the)", "République centrafricaine (la)", "CF", "CAF", "140"},
{"Sri Lanka", "Sri Lanka", "LK", "LKA", "144"},
{"Chad", "Tchad (le)", "TD", "TCD", "148"},
{"Chile", "Chili (le)", "CL", "CHL", "152"},
{"China", "Chine (la)", "CN", "CHN", "156"},
{"Taiwan (Province of China)", "Taïwan (Province de Chine)", "TW", "TWN", "158"},
{"Christmas Island", "Christmas (l'Île)", "CX", "CXR", "162"},
{"Cocos (Keeling) Islands (the)", "Cocos (les Îles)/ Keeling (les Îles)", "CC", "CCK", "166"},
{"Colombia", "Colombie (la)", "CO", "COL", "170"},
{"Comoros (the)", "Comores (les)", "KM", "COM", "174"},
{"Mayotte", "Mayotte", "YT", "MYT", "175"},
{"Congo (the)", "Congo (le)", "CG", "COG", "178"},
{"Congo (the Democratic Republic of the)", "Congo (la République démocratique du)", "CD", "COD", "180"},
{"Cook Islands (the)", "Cook (les Îles)", "CK", "COK", "184"},
{"Costa Rica", "Costa Rica (le)", "CR", "CRI", "188"},
{"Croatia", "Croatie (la)", "HR", "HRV", "191"},
{"Cuba", "Cuba", "CU", "CUB", "192"},
{"Cyprus", "Chypre", "CY", "CYP", "196"},
{"Czech Republic (the)", "tchèque (la République)", "CZ", "CZE", "203"},
{"Benin", "Bénin (le)", "BJ", "BEN", "204"},
{"Denmark", "Danemark (le)", "DK", "DNK", "208"},
{"Dominica", "Dominique (la)", "DM", "DMA", "212"},
{"Dominican Republic (the)", "dominicaine (la République)", "DO", "DOM", "214"},
{"Ecuador", "Équateur (l')", "EC", "ECU", "218"},
{"El Salvador", "El Salvador", "SV", "SLV", "222"},
{"Equatorial Guinea", "Guinée équatoriale (la)", "GQ", "GNQ", "226"},
{"Ethiopia", "Éthiopie (l')", "ET", "ETH", "231"},
{"Eritrea", "Érythrée (l')", "ER", "ERI", "232"},
{"Estonia", "Estonie (l')", "EE", "EST", "233"},
{"Faroe Islands (the)", "Féroé (les Îles)", "FO", "FRO", "234"},
{"Falkland Islands (the) [Malvinas]", "Falkland (les Îles)/Malouines (les Îles)", "FK", "FLK", "238"},
{"South Georgia and the South Sandwich Islands", "Géorgie du Sud-et-les Îles Sandwich du Sud (la)", "GS", "SGS", "239"},
{"Fiji", "Fidji (les)", "FJ", "FJI", "242"},
{"Finland", "Finlande (la)", "FI", "FIN", "246"},
{"Åland Islands", "Åland(les Îles)", "AX", "ALA", "248"},
{"France", "France (la)", "FR", "FRA", "250"},
{"French Guiana", "Guyane française (la )", "GF", "GUF", "254"},
{"French Polynesia", "Polynésie française (la)", "PF", "PYF", "258"},
{"French Southern Territories (the)", "Terres australes françaises (les)", "TF", "ATF", "260"},
{"Djibouti", "Djibouti", "DJ", "DJI", "262"},
{"Gabon", "Gabon (le)", "GA", "GAB", "266"},
{"Georgia", "Géorgie (la)", "GE", "GEO", "268"},
{"Gambia (the)", "Gambie (la)", "GM", "GMB", "270"},
{"Palestine, State of", "Palestine, État de", "PS", "PSE", "275"},
{"Germany", "Allemagne (l')", "DE", "DEU", "276"},
{"Ghana", "Ghana (le)", "GH", "GHA", "288"},
{"Gibraltar", "Gibraltar", "GI", "GIB", "292"},
{"Kiribati", "Kiribati", "KI", "KIR", "296"},
{"Greece", "Grèce (la)", "GR", "GRC", "300"},
{"Greenland", "Groenland (le)", "GL", "GRL", "304"},
{"Grenada", "Grenade (la)", "GD", "GRD", "308"},
{"Guadeloupe", "Guadeloupe (la)", "GP", "GLP", "312"},
{"Guam", "Guam", "GU", "GUM", "316"},
{"Guatemala", "Guatemala (le)", "GT", "GTM", "320"},
{"Guinea", "Guinée (la)", "GN", "GIN", "324"},
{"Guyana", "Guyana (le)", "GY", "GUY", "328"},
{"Haiti", "Haïti", "HT", "HTI", "332"},
{"Heard Island and McDonald Islands", "Heard-et-Îles MacDonald (l'Île)", "HM", "HMD", "334"},
{"Holy See (the)", "Saint-Siège (le)", "VA", "VAT", "336"},
{"Honduras", "Honduras (le)", "HN", "HND", "340"},
{"Hong Kong", "Hong Kong", "HK", "HKG", "344"},
{"Hungary", "Hongrie (la)", "HU", "HUN", "348"},
{"Iceland", "Islande (l')", "IS", "ISL", "352"},
{"India", "Inde (l')", "IN", "IND", "356"},
{"Indonesia", "Indonésie (l')", "ID", "IDN", "360"},
{"Iran (Islamic Republic of)", "Iran (République Islamique d')", "IR", "IRN", "364"},
{"Iraq", "Iraq (l')", "IQ", "IRQ", "368"},
{"Ireland", "Irlande (l')", "IE", "IRL", "372"},
{"Israel", "Israël", "IL", "ISR", "376"},
{"Italy", "Italie (l')", "IT", "ITA", "380"},
{"Côte d'Ivoire", "Côte d'Ivoire (la)", "CI", "CIV", "384"},
{"Jamaica", "Jamaïque (la)", "JM", "JAM", "388"},
{"Japan", "Japon (le)", "JP", "JPN", "392"},
{"Kazakhstan", "Kazakhstan (le)", "KZ", "KAZ", "398"},
{"Jordan", "Jordanie (la)", "JO", "JOR", "400"},
{"Kenya", "Kenya (le)", "KE", "KEN", "404"},
{"Korea (the Democratic People's Republic of)", "Corée (la République populaire démocratique de)", "KP", "PRK", "408"},
{"Korea (the Republic of)", "Corée (la République de)", "KR", "KOR", "410"},
{"Kuwait", "Koweït (le)", "KW", "KWT", "414"},
{"Kyrgyzstan", "Kirghizistan (le)", "KG", "KGZ", "417"},
{"Lao People's Democratic Republic (the)", "Lao, République démocratique populaire", "LA", "LAO", "418"},
{"Lebanon", "Liban (le)", "LB", "LBN", "422"},
{"Lesotho", "Lesotho (le)", "LS", "LSO", "426"},
{"Latvia", "Lettonie (la)", "LV", "LVA", "428"},
{"Liberia", "Libéria (le)", "LR", "LBR", "430"},
{"Libya", "Libye (la)", "LY", "LBY", "434"},
{"Liechtenstein", "Liechtenstein (le)", "LI", "LIE", "438"},
{"Lithuania", "Lituanie (la)", "LT", "LTU", "440"},
{"Luxembourg", "Luxembourg (le)", "LU", "LUX", "442"},
{"Macao", "Macao", "MO", "MAC", "446"},
{"Madagascar", "Madagascar", "MG", "MDG", "450"},
{"Malawi", "Malawi (le)", "MW", "MWI", "454"},
{"Malaysia", "Malaisie (la)", "MY", "MYS", "458"},
{"Maldives", "Maldives (les)", "MV", "MDV", "462"},
{"Mali", "Mali (le)", "ML", "MLI", "466"},
{"Malta", "Malte", "MT", "MLT", "470"},
{"Martinique", "Martinique (la)", "MQ", "MTQ", "474"},
{"Mauritania", "Mauritanie (la)", "MR", "MRT", "478"},
{"Mauritius", "Maurice", "MU", "MUS", "480"},
{"Mexico", "Mexique (le)", "MX", "MEX", "484"},
{"Monaco", "Monaco", "MC", "MCO", "492"},
{"Mongolia", "Mongolie (la)", "MN", "MNG", "496"},
{"Moldova (the Republic of)", "Moldova , République de", "MD", "MDA", "498"},
{"Montenegro", "Monténégro (le)", "ME", "MNE", "499"},
{"Montserrat", "Montserrat", "MS", "MSR", "500"},
{"Morocco", "Maroc (le)", "MA", "MAR", "504"},
{"Mozambique", "Mozambique (le)", "MZ", "MOZ", "508"},
{"Oman", "Oman", "OM", "OMN", "512"},
{"Namibia", "Namibie (la)", "NA", "NAM", "516"},
{"Nauru", "Nauru", "NR", "NRU", "520"},
{"Nepal", "Népal (le)", "NP", "NPL", "524"},
{"Netherlands (the)", "Pays-Bas (les)", "NL", "NLD", "528"},
{"Curaçao", "Curaçao", "CW", "CUW", "531"},
{"Aruba", "Aruba", "AW", "ABW", "533"},
{"Sint Maarten (Dutch part)", "Saint-Martin (partie néerlandaise)", "SX", "SXM", "534"},
{"Bonaire, Sint Eustatius and Saba", "Bonaire, Saint-Eustache et Saba", "BQ", "BES", "535"},
{"New Caledonia", "Nouvelle-Calédonie (la)", "NC", "NCL", "540"},
{"Vanuatu", "Vanuatu (le)", "VU", "VUT", "548"},
{"New Zealand", "Nouvelle-Zélande (la)", "NZ", "NZL", "554"},
{"Nicaragua", "Nicaragua (le)", "NI", "NIC", "558"},
{"Niger (the)", "Niger (le)", "NE", "NER", "562"},
{"Nigeria", "Nigéria (le)", "NG", "NGA", "566"},
{"Niue", "Niue", "NU", "NIU", "570"},
{"Norfolk Island", "Norfolk (l'Île)", "NF", "NFK", "574"},
{"Norway", "Norvège (la)", "NO", "NOR", "578"},
{"Northern Mariana Islands (the)", "Mariannes du Nord (les Îles)", "MP", "MNP", "580"},
{"United States Minor Outlying Islands (the)", "Îles mineures éloignées des États-Unis (les)", "UM", "UMI", "581"},
{"Micronesia (Federated States of)", "Micronésie (États fédérés de)", "FM", "FSM", "583"},
{"Marshall Islands (the)", "Marshall (Îles)", "MH", "MHL", "584"},
{"Palau", "Palaos (les)", "PW", "PLW", "585"},
{"Pakistan", "Pakistan (le)", "PK", "PAK", "586"},
{"Panama", "Panama (le)", "PA", "PAN", "591"},
{"Papua New Guinea", "Papouasie-Nouvelle-Guinée (la)", "PG", "PNG", "598"},
{"Paraguay", "Paraguay (le)", "PY", "PRY", "600"},
{"Peru", "Pérou (le)", "PE", "PER", "604"},
{"Philippines (the)", "Philippines (les)", "PH", "PHL", "608"},
{"Pitcairn", "Pitcairn", "PN", "PCN", "612"},
{"Poland", "Pologne (la)", "PL", "POL", "616"},
{"Portugal", "Portugal (le)", "PT", "PRT", "620"},
{"Guinea-Bissau", "Guinée-Bissau (la)", "GW", "GNB", "624"},
{"Timor-Leste", "Timor-Leste (le)", "TL", "TLS", "626"},
{"Puerto Rico", "Porto Rico", "PR", "PRI", "630"},
{"Qatar", "Qatar (le)", "QA", "QAT", "634"},
{"Réunion", "Réunion (La)", "RE", "REU", "638"},
{"Romania", "Roumanie (la)", "RO", "ROU", "642"},
{"Russian Federation (the)", "Russie (la Fédération de)", "RU", "RUS", "643"},
{"Rwanda", "Rwanda (le)", "RW", "RWA", "646"},
{"Saint Barthélemy", "Saint-Barthélemy", "BL", "BLM", "652"},
{"Saint Helena, Ascension and Tristan da Cunha", "Sainte-Hélène, Ascension et Tristan da Cunha", "SH", "SHN", "654"},
{"Saint Kitts and Nevis", "Saint-Kitts-et-Nevis", "KN", "KNA", "659"},
{"Anguilla", "Anguilla", "AI", "AIA", "660"},
{"Saint Lucia", "Sainte-Lucie", "LC", "LCA", "662"},
{"Saint Martin (French part)", "Saint-Martin (partie française)", "MF", "MAF", "663"},
{"Saint Pierre and Miquelon", "Saint-Pierre-et-Miquelon", "PM", "SPM", "666"},
{"Saint Vincent and the Grenadines", "Saint-Vincent-et-les Grenadines", "VC", "VCT", "670"},
{"San Marino", "Saint-Marin", "SM", "SMR", "674"},
{"Sao Tome and Principe", "Sao Tomé-et-Principe", "ST", "STP", "678"},
{"Saudi Arabia", "Arabie saoudite (l')", "SA", "SAU", "682"},
{"Senegal", "Sénégal (le)", "SN", "SEN", "686"},
{"Serbia", "Serbie (la)", "RS", "SRB", "688"},
{"Seychelles", "Seychelles (les)", "SC", "SYC", "690"},
{"Sierra Leone", "Sierra Leone (la)", "SL", "SLE", "694"},
{"Singapore", "Singapour", "SG", "SGP", "702"},
{"Slovakia", "Slovaquie (la)", "SK", "SVK", "703"},
{"Viet Nam", "Viet Nam (le)", "VN", "VNM", "704"},
{"Slovenia", "Slovénie (la)", "SI", "SVN", "705"},
{"Somalia", "Somalie (la)", "SO", "SOM", "706"},
{"South Africa", "Afrique du Sud (l')", "ZA", "ZAF", "710"},
{"Zimbabwe", "Zimbabwe (le)", "ZW", "ZWE", "716"},
{"Spain", "Espagne (l')", "ES", "ESP", "724"},
{"South Sudan", "Soudan du Sud (le)", "SS", "SSD", "728"},
{"Sudan (the)", "Soudan (le)", "SD", "SDN", "729"},
{"Western Sahara*", "Sahara occidental (le)*", "EH", "ESH", "732"},
{"Suriname", "Suriname (le)", "SR", "SUR", "740"},
{"Svalbard and Jan Mayen", "Svalbard et l'Île Jan Mayen (le)", "SJ", "SJM", "744"},
{"Swaziland", "Swaziland (le)", "SZ", "SWZ", "748"},
{"Sweden", "Suède (la)", "SE", "SWE", "752"},
{"Switzerland", "Suisse (la)", "CH", "CHE", "756"},
{"Syrian Arab Republic", "République arabe syrienne (la)", "SY", "SYR", "760"},
{"Tajikistan", "Tadjikistan (le)", "TJ", "TJK", "762"},
{"Thailand", "Thaïlande (la)", "TH", "THA", "764"},
{"Togo", "Togo (le)", "TG", "TGO", "768"},
{"Tokelau", "Tokelau (les)", "TK", "TKL", "772"},
{"Tonga", "Tonga (les)", "TO", "TON", "776"},
{"Trinidad and Tobago", "Trinité-et-Tobago (la)", "TT", "TTO", "780"},
{"United Arab Emirates (the)", "Émirats arabes unis (les)", "AE", "ARE", "784"},
{"Tunisia", "Tunisie (la)", "TN", "TUN", "788"},
{"Turkey", "Turquie (la)", "TR", "TUR", "792"},
{"Turkmenistan", "Turkménistan (le)", "TM", "TKM", "795"},
{"Turks and Caicos Islands (the)", "Turks-et-Caïcos (les Îles)", "TC", "TCA", "796"},
{"Tuvalu", "Tuvalu (les)", "TV", "TUV", "798"},
{"Uganda", "Ouganda (l')", "UG", "UGA", "800"},
{"Ukraine", "Ukraine (l')", "UA", "UKR", "804"},
{"Macedonia (the former Yugoslav Republic of)", "Macédoine (l'exRépublique yougoslave de)", "MK", "MKD", "807"},
{"Egypt", "Égypte (l')", "EG", "EGY", "818"},
{"United Kingdom of Great Britain and Northern Ireland (the)", "Royaume-Uni de Grande-Bretagne et d'Irlande du Nord (le)", "GB", "GBR", "826"},
{"Guernsey", "Guernesey", "GG", "GGY", "831"},
{"Jersey", "Jersey", "JE", "JEY", "832"},
{"Isle of Man", "Île de Man", "IM", "IMN", "833"},
{"Tanzania, United Republic of", "Tanzanie, République-Unie de", "TZ", "TZA", "834"},
{"United States of America (the)", "États-Unis d'Amérique (les)", "US", "USA", "840"},
{"Virgin Islands (U.S.)", "Vierges des États-Unis (les Îles)", "VI", "VIR", "850"},
{"Burkina Faso", "Burkina Faso (le)", "BF", "BFA", "854"},
{"Uruguay", "Uruguay (l')", "UY", "URY", "858"},
{"Uzbekistan", "Ouzbékistan (l')", "UZ", "UZB", "860"},
{"Venezuela (Bolivarian Republic of)", "Venezuela (République bolivarienne du)", "VE", "VEN", "862"},
{"Wallis and Futuna", "Wallis-et-Futuna", "WF", "WLF", "876"},
{"Samoa", "Samoa (le)", "WS", "WSM", "882"},
{"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",
}

View file

@ -1,268 +0,0 @@
package govalidator
import (
"errors"
"fmt"
"html"
"math"
"path"
"regexp"
"strings"
"unicode"
"unicode/utf8"
)
// Contains check if the string contains the substring.
func Contains(str, substring string) bool {
return strings.Contains(str, substring)
}
// Matches check if string matches the pattern (pattern is regular expression)
// In case of error return false
func Matches(str, pattern string) bool {
match, _ := regexp.MatchString(pattern, str)
return match
}
// LeftTrim trim characters from the left-side of the input.
// If second argument is empty, it's will be remove leading spaces.
func LeftTrim(str, chars string) string {
pattern := ""
if chars == "" {
pattern = "^\\s+"
} else {
pattern = "^[" + chars + "]+"
}
r, _ := regexp.Compile(pattern)
return string(r.ReplaceAll([]byte(str), []byte("")))
}
// RightTrim trim characters from the right-side of the input.
// If second argument is empty, it's will be remove spaces.
func RightTrim(str, chars string) string {
pattern := ""
if chars == "" {
pattern = "\\s+$"
} else {
pattern = "[" + chars + "]+$"
}
r, _ := regexp.Compile(pattern)
return string(r.ReplaceAll([]byte(str), []byte("")))
}
// Trim trim characters from both sides of the input.
// If second argument is empty, it's will be remove spaces.
func Trim(str, chars string) string {
return LeftTrim(RightTrim(str, chars), chars)
}
// WhiteList remove characters that do not appear in the whitelist.
func WhiteList(str, chars string) string {
pattern := "[^" + chars + "]+"
r, _ := regexp.Compile(pattern)
return string(r.ReplaceAll([]byte(str), []byte("")))
}
// BlackList remove characters that appear in the blacklist.
func BlackList(str, chars string) string {
pattern := "[" + chars + "]+"
r, _ := regexp.Compile(pattern)
return string(r.ReplaceAll([]byte(str), []byte("")))
}
// StripLow remove characters with a numerical value < 32 and 127, mostly control characters.
// If keep_new_lines is true, newline characters are preserved (\n and \r, hex 0xA and 0xD).
func StripLow(str string, keepNewLines bool) string {
chars := ""
if keepNewLines {
chars = "\x00-\x09\x0B\x0C\x0E-\x1F\x7F"
} else {
chars = "\x00-\x1F\x7F"
}
return BlackList(str, chars)
}
// ReplacePattern replace regular expression pattern in string
func ReplacePattern(str, pattern, replace string) string {
r, _ := regexp.Compile(pattern)
return string(r.ReplaceAll([]byte(str), []byte(replace)))
}
// Escape replace <, >, & and " with HTML entities.
var Escape = html.EscapeString
func addSegment(inrune, segment []rune) []rune {
if len(segment) == 0 {
return inrune
}
if len(inrune) != 0 {
inrune = append(inrune, '_')
}
inrune = append(inrune, segment...)
return inrune
}
// UnderscoreToCamelCase converts from underscore separated form to camel case form.
// Ex.: my_func => MyFunc
func UnderscoreToCamelCase(s string) string {
return strings.Replace(strings.Title(strings.Replace(strings.ToLower(s), "_", " ", -1)), " ", "", -1)
}
// CamelCaseToUnderscore converts from camel case form to underscore separated form.
// Ex.: MyFunc => my_func
func CamelCaseToUnderscore(str string) string {
var output []rune
var segment []rune
for _, r := range str {
if !unicode.IsLower(r) {
output = addSegment(output, segment)
segment = nil
}
segment = append(segment, unicode.ToLower(r))
}
output = addSegment(output, segment)
return string(output)
}
// Reverse return reversed string
func Reverse(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
}
// GetLines split string by "\n" and return array of lines
func GetLines(s string) []string {
return strings.Split(s, "\n")
}
// GetLine return specified line of multiline string
func GetLine(s string, index int) (string, error) {
lines := GetLines(s)
if index < 0 || index >= len(lines) {
return "", errors.New("line index out of bounds")
}
return lines[index], nil
}
// RemoveTags remove all tags from HTML string
func RemoveTags(s string) string {
return ReplacePattern(s, "<[^>]*>", "")
}
// SafeFileName return safe string that can be used in file names
func SafeFileName(str string) string {
name := strings.ToLower(str)
name = path.Clean(path.Base(name))
name = strings.Trim(name, " ")
separators, err := regexp.Compile(`[ &_=+:]`)
if err == nil {
name = separators.ReplaceAllString(name, "-")
}
legal, err := regexp.Compile(`[^[:alnum:]-.]`)
if err == nil {
name = legal.ReplaceAllString(name, "")
}
for strings.Contains(name, "--") {
name = strings.Replace(name, "--", "-", -1)
}
return name
}
// NormalizeEmail canonicalize an email address.
// The local part of the email address is lowercased for all domains; the hostname is always lowercased and
// the local part of the email address is always lowercased for hosts that are known to be case-insensitive (currently only GMail).
// Normalization follows special rules for known providers: currently, GMail addresses have dots removed in the local part and
// are stripped of tags (e.g. some.one+tag@gmail.com becomes someone@gmail.com) and all @googlemail.com addresses are
// normalized to @gmail.com.
func NormalizeEmail(str string) (string, error) {
if !IsEmail(str) {
return "", fmt.Errorf("%s is not an email", str)
}
parts := strings.Split(str, "@")
parts[0] = strings.ToLower(parts[0])
parts[1] = strings.ToLower(parts[1])
if parts[1] == "gmail.com" || parts[1] == "googlemail.com" {
parts[1] = "gmail.com"
parts[0] = strings.Split(ReplacePattern(parts[0], `\.`, ""), "+")[0]
}
return strings.Join(parts, "@"), nil
}
// Truncate a string to the closest length without breaking words.
func Truncate(str string, length int, ending string) string {
var aftstr, befstr string
if len(str) > length {
words := strings.Fields(str)
before, present := 0, 0
for i := range words {
befstr = aftstr
before = present
aftstr = aftstr + words[i] + " "
present = len(aftstr)
if present > length && i != 0 {
if (length - before) < (present - length) {
return Trim(befstr, " /\\.,\"'#!?&@+-") + ending
}
return Trim(aftstr, " /\\.,\"'#!?&@+-") + ending
}
}
}
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
}

File diff suppressed because it is too large Load diff

View file

@ -1,15 +0,0 @@
box: golang
build:
steps:
- setup-go-workspace
- script:
name: go get
code: |
go version
go get -t ./...
- script:
name: go test
code: |
go test -race ./...

6
vendor/vendor.json vendored
View file

@ -76,12 +76,6 @@
"revision": "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a", "revision": "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a",
"revisionTime": "2015-10-22T06:55:26Z" "revisionTime": "2015-10-22T06:55:26Z"
}, },
{
"checksumSHA1": "ddYc7mKe3g1x1UUKBrGR4vArJs8=",
"path": "github.com/asaskevich/govalidator",
"revision": "065ea97278837088c52c0cd0d963473f61b2d98c",
"revisionTime": "2017-05-13T08:31:01Z"
},
{ {
"checksumSHA1": "WNfR3yhLjRC5/uccgju/bwrdsxQ=", "checksumSHA1": "WNfR3yhLjRC5/uccgju/bwrdsxQ=",
"path": "github.com/aws/aws-sdk-go/aws", "path": "github.com/aws/aws-sdk-go/aws",

File diff suppressed because one or more lines are too long

View file

@ -120,6 +120,7 @@ Prometheus.Graph.prototype.initialize = function() {
showTodayButton: true, showTodayButton: true,
showClear: true, showClear: true,
showClose: true, showClose: true,
timeZone: 'UTC',
}); });
if (self.options.end_input) { if (self.options.end_input) {
self.endDate.data('DateTimePicker').date(self.options.end_input); self.endDate.data('DateTimePicker').date(self.options.end_input);
@ -620,7 +621,7 @@ Prometheus.Graph.prototype.updateGraph = function() {
var yAxis = new Rickshaw.Graph.Axis.Y({ var yAxis = new Rickshaw.Graph.Axis.Y({
graph: self.rickshawGraph, graph: self.rickshawGraph,
orientation: "left", orientation: "left",
tickFormat: Rickshaw.Fixtures.Number.formatKMBT, tickFormat: this.formatKMBT,
element: self.yAxis[0], element: self.yAxis[0],
}); });
@ -733,6 +734,49 @@ Prometheus.Graph.prototype.remove = function() {
self.handleChange(); self.handleChange();
}; };
Prometheus.Graph.prototype.formatKMBT = function(y) {
var abs_y = Math.abs(y);
if (abs_y >= 1e24) {
return (y / 1e24).toString() + "Y";
} else if (abs_y >= 1e21) {
return (y / 1e21).toString() + "Z";
} else if (abs_y >= 1e18) {
return (y / 1e18).toString() + "E";
} else if (abs_y >= 1e15) {
return (y / 1e15).toString() + "P";
} else if (abs_y >= 1e12) {
return (y / 1e12).toString() + "T";
} else if (abs_y >= 1e9) {
return (y / 1e9).toString() + "G";
} else if (abs_y >= 1e6) {
return (y / 1e6).toString() + "M";
} else if (abs_y >= 1e3) {
return (y / 1e3).toString() + "k";
} else if (abs_y >= 1) {
return y
} else if (abs_y === 0) {
return y
} else if (abs_y <= 1e-24) {
return (y / 1e-24).toString() + "y";
} else if (abs_y <= 1e-21) {
return (y / 1e-21).toString() + "z";
} else if (abs_y <= 1e-18) {
return (y / 1e-18).toString() + "a";
} else if (abs_y <= 1e-15) {
return (y / 1e-15).toString() + "f";
} else if (abs_y <= 1e-12) {
return (y / 1e-12).toString() + "p";
} else if (abs_y <= 1e-9) {
return (y / 1e-9).toString() + "n";
} else if (abs_y <= 1e-6) {
return (y / 1e-6).toString() + "µ";
} else if (abs_y <=1e-3) {
return (y / 1e-3).toString() + "m";
} else if (abs_y <= 1) {
return y
}
}
function escapeHTML(string) { function escapeHTML(string) {
var entityMap = { var entityMap = {
"&": "&amp;", "&": "&amp;",

View file

@ -77,7 +77,7 @@
<input <input
class="pull-left date_input input" class="pull-left date_input input"
id="end{{id}}" id="end{{id}}"
title="End time of graph" title="End time of graph (UTC)"
placeholder="Until" placeholder="Until"
data-format="yyyy-MM-dd" data-format="yyyy-MM-dd"
type="text" type="text"

View file

@ -12,89 +12,102 @@
(function(jQuery){ (function(jQuery){
jQuery.hotkeys = { jQuery.hotkeys = {
version: "0.8", version: "0.8+",
specialKeys: { specialKeys: {
8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", 8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del",
96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta" 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 188: ",", 190: ".",
}, 191: "/", 224: "meta"
},
shiftNums: { shiftNums: {
"`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
"8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<", "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
".": ">", "/": "?", "\\": "|" ".": ">", "/": "?", "\\": "|"
} }
}; };
function keyHandler( handleObj ) { function keyHandler( handleObj ) {
// Only care when a possible input has been specified
if ( typeof handleObj.data !== "string" ) {
return;
}
var origHandler = handleObj.handler, var origHandler = handleObj.handler,
keys = handleObj.data.toLowerCase().split(" "), //use namespace as keys so it works with event delegation as well
textAcceptingInputTypes = ["text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime", "datetime-local", "search", "color"]; //will also allow removing listeners of a specific key combination
//and support data objects
keys = (handleObj.namespace || "").toLowerCase().split(" ");
keys = jQuery.map(keys, function(key) { return key.split("."); });
handleObj.handler = function( event ) { //no need to modify handler if no keys specified
// Don't fire in text-accepting inputs that we didn't directly bind to //Added keys[0].substring(0, 12) to work with jQuery ui 1.9.0
if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) || //Added accordion, tabs and menu, then jquery ui can use keys.
jQuery.inArray(event.target.type, textAcceptingInputTypes) > -1 ) ) {
return;
}
// Keypress represents characters, not special keys if (keys.length === 1 && (keys[0] === "" ||
var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ], keys[0].substring(0, 12) === "autocomplete" ||
character = String.fromCharCode( event.which ).toLowerCase(), keys[0].substring(0, 9) === "accordion" ||
key, modif = "", possible = {}; keys[0].substring(0, 4) === "tabs" ||
keys[0].substring(0, 4) === "menu")) {
return;
}
// check combinations (alt|ctrl|shift+anything) handleObj.handler = function( event ) {
if ( event.altKey && special !== "alt" ) { // Don't fire in text-accepting inputs that we didn't directly bind to
modif += "alt+"; // important to note that $.fn.prop is only available on jquery 1.6+
} if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) ||
event.target.type === "text" || $(event.target).prop('contenteditable') == 'true' )) {
return;
}
if ( event.ctrlKey && special !== "ctrl" ) { // Keypress represents characters, not special keys
modif += "ctrl+"; var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ],
} character = String.fromCharCode( event.which ).toLowerCase(),
key, modif = "", possible = {};
// TODO: Need to make sure this works consistently across platforms // check combinations (alt|ctrl|shift+anything)
if ( event.metaKey && !event.ctrlKey && special !== "meta" ) { if ( event.altKey && special !== "alt" ) {
modif += "meta+"; modif += "alt_";
} }
if ( event.shiftKey && special !== "shift" ) { if ( event.ctrlKey && special !== "ctrl" ) {
modif += "shift+"; modif += "ctrl_";
} }
if ( special ) { // TODO: Need to make sure this works consistently across platforms
possible[ modif + special ] = true; if ( event.metaKey && !event.ctrlKey && special !== "meta" ) {
modif += "meta_";
}
} else { if ( event.shiftKey && special !== "shift" ) {
possible[ modif + character ] = true; modif += "shift_";
possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true; }
// "$" can be triggered as "Shift+4" or "Shift+$" or just "$" if ( special ) {
if ( modif === "shift+" ) { possible[ modif + special ] = true;
possible[ jQuery.hotkeys.shiftNums[ character ] ] = true;
}
}
for ( var i = 0, l = keys.length; i < l; i++ ) { } else {
if ( possible[ keys[i] ] ) { possible[ modif + character ] = true;
return origHandler.apply( this, arguments ); possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true;
}
}
};
}
jQuery.each([ "keydown", "keyup", "keypress" ], function() { // "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
jQuery.event.special[ this ] = { add: keyHandler }; if ( modif === "shift_" ) {
}); possible[ jQuery.hotkeys.shiftNums[ character ] ] = true;
}
}
for ( var i = 0, l = keys.length; i < l; i++ ) {
if ( possible[ keys[i] ] ) {
return origHandler.apply( this, arguments );
}
}
};
}
jQuery.each([ "keydown", "keyup", "keypress" ], function() {
jQuery.event.special[ this ] = { add: keyHandler };
});
})( jQuery ); })( jQuery );

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,7 @@
/*! /*!
* jQuery.selection - jQuery Plugin * jQuery.selection - jQuery Plugin
* *
* Copyright (c) 2010-2012 IWASAKI Koji (@madapaja). * Copyright (c) 2010-2014 IWASAKI Koji (@madapaja).
* http://blog.madapaja.net/ * http://blog.madapaja.net/
* Under The MIT License * Under The MIT License
* *
@ -26,13 +26,13 @@
*/ */
(function($, win, doc) { (function($, win, doc) {
/** /**
* 要素の文字列選択状態を取得します * get caret status of the selection of the element
* *
* @param {Element} element 対象要素 * @param {Element} element target DOM element
* @return {Object} return * @return {Object} return
* @return {String} return.text 選択されている文字列 * @return {String} return.text selected text
* @return {Integer} return.start 選択開始位置 * @return {Number} return.start start position of the selection
* @return {Integer} return.end 選択終了位置 * @return {Number} return.end end position of the selection
*/ */
var _getCaretInfo = function(element){ var _getCaretInfo = function(element){
var res = { var res = {
@ -42,13 +42,13 @@
}; };
if (!element.value) { if (!element.value) {
/* 値がない、もしくは空文字列 */ /* no value or empty string */
return res; return res;
} }
try { try {
if (win.getSelection) { if (win.getSelection) {
/* IE 以外 */ /* except IE */
res.start = element.selectionStart; res.start = element.selectionStart;
res.end = element.selectionEnd; res.end = element.selectionEnd;
res.text = element.value.slice(res.start, res.end); res.text = element.value.slice(res.start, res.end);
@ -57,8 +57,7 @@
element.focus(); element.focus();
var range = doc.selection.createRange(), var range = doc.selection.createRange(),
range2 = doc.body.createTextRange(), range2 = doc.body.createTextRange();
tmpLength;
res.text = range.text; res.text = range.text;
@ -74,24 +73,24 @@
res.end = res.start + range.text.length; res.end = res.start + range.text.length;
} }
} catch (e) { } catch (e) {
/* あきらめる */ /* give up */
} }
return res; return res;
}; };
/** /**
* 要素に対するキャレット操作 * caret operation for the element
* @type {Object} * @type {Object}
*/ */
var _CaretOperation = { var _CaretOperation = {
/** /**
* 要素のキャレット位置を取得します * get caret position
* *
* @param {Element} element 対象要素 * @param {Element} element target element
* @return {Object} return * @return {Object} return
* @return {Integer} return.start 選択開始位置 * @return {Number} return.start start position for the selection
* @return {Integer} return.end 選択終了位置 * @return {Number} return.end end position for the selection
*/ */
getPos: function(element) { getPos: function(element) {
var tmp = _getCaretInfo(element); var tmp = _getCaretInfo(element);
@ -99,20 +98,20 @@
}, },
/** /**
* 要素のキャレット位置を設定します * set caret position
* *
* @param {Element} element 対象要素 * @param {Element} element target element
* @param {Object} toRange 設定するキャレット位置 * @param {Object} toRange caret position
* @param {Integer} toRange.start 選択開始位置 * @param {Number} toRange.start start position for the selection
* @param {Integer} toRange.end 選択終了位置 * @param {Number} toRange.end end position for the selection
* @param {String} caret キャレットモード "keep" | "start" | "end" のいずれか * @param {String} caret caret mode: any of the following: "keep" | "start" | "end"
*/ */
setPos: function(element, toRange, caret) { setPos: function(element, toRange, caret) {
caret = this._caretMode(caret); caret = this._caretMode(caret);
if (caret == 'start') { if (caret === 'start') {
toRange.end = toRange.start; toRange.end = toRange.start;
} else if (caret == 'end') { } else if (caret === 'end') {
toRange.start = toRange.end; toRange.start = toRange.end;
} }
@ -135,29 +134,29 @@
element.setSelectionRange(toRange.start, toRange.end); element.setSelectionRange(toRange.start, toRange.end);
} }
} catch (e) { } catch (e) {
/* あきらめる */ /* give up */
} }
}, },
/** /**
* 要素内の選択文字列を取得します * get selected text
* *
* @param {Element} element 対象要素 * @param {Element} element target element
* @return {String} return 選択文字列 * @return {String} return selected text
*/ */
getText: function(element) { getText: function(element) {
return _getCaretInfo(element).text; return _getCaretInfo(element).text;
}, },
/** /**
* キャレットモードを選択します * get caret mode
* *
* @param {String} caret キャレットモード * @param {String} caret caret mode
* @return {String} return "keep" | "start" | "end" のいずれか * @return {String} return any of the following: "keep" | "start" | "end"
*/ */
_caretMode: function(caret) { _caretMode: function(caret) {
caret = caret || "keep"; caret = caret || "keep";
if (caret == false) { if (caret === false) {
caret = 'end'; caret = 'end';
} }
@ -175,11 +174,11 @@
}, },
/** /**
* 選択文字列を置き換えます * replace selected text
* *
* @param {Element} element 対象要素 * @param {Element} element target element
* @param {String} text 置き換える文字列 * @param {String} text replacement text
* @param {String} caret キャレットモード "keep" | "start" | "end" のいずれか * @param {String} caret caret mode: any of the following: "keep" | "start" | "end"
*/ */
replace: function(element, text, caret) { replace: function(element, text, caret) {
var tmp = _getCaretInfo(element), var tmp = _getCaretInfo(element),
@ -194,11 +193,11 @@
}, },
/** /**
* 文字列を選択文字列の前に挿入します * insert before the selected text
* *
* @param {Element} element 対象要素 * @param {Element} element target element
* @param {String} text 挿入文字列 * @param {String} text insertion text
* @param {String} caret キャレットモード "keep" | "start" | "end" のいずれか * @param {String} caret caret mode: any of the following: "keep" | "start" | "end"
*/ */
insertBefore: function(element, text, caret) { insertBefore: function(element, text, caret) {
var tmp = _getCaretInfo(element), var tmp = _getCaretInfo(element),
@ -213,11 +212,11 @@
}, },
/** /**
* 文字列を選択文字列の後に挿入します * insert after the selected text
* *
* @param {Element} element 対象要素 * @param {Element} element target element
* @param {String} text 挿入文字列 * @param {String} text insertion text
* @param {String} caret キャレットモード "keep" | "start" | "end" のいずれか * @param {String} caret caret mode: any of the following: "keep" | "start" | "end"
*/ */
insertAfter: function(element, text, caret) { insertAfter: function(element, text, caret) {
var tmp = _getCaretInfo(element), var tmp = _getCaretInfo(element),
@ -232,16 +231,16 @@
} }
}; };
/* jQuery.selection を追加 */ /* add jQuery.selection */
$.extend({ $.extend({
/** /**
* ウィンドウの選択されている文字列を取得 * get selected text on the window
* *
* @param {String} mode 選択モード "text" | "html" のいずれか * @param {String} mode selection mode: any of the following: "text" | "html"
* @return {String} return * @return {String} return
*/ */
selection: function(mode) { selection: function(mode) {
var getText = ((mode || 'text').toLowerCase() == 'text'); var getText = ((mode || 'text').toLowerCase() === 'text');
try { try {
if (win.getSelection) { if (win.getSelection) {
@ -272,14 +271,14 @@
} }
} }
} catch (e) { } catch (e) {
/* あきらめる */ /* give up */
} }
return ''; return '';
} }
}); });
/* selection を追加 */ /* add selection */
$.fn.extend({ $.fn.extend({
selection: function(mode, opts) { selection: function(mode, opts) {
opts = opts || {}; opts = opts || {};
@ -287,71 +286,66 @@
switch (mode) { switch (mode) {
/** /**
* selection('getPos') * selection('getPos')
* キャレット位置を取得します * get caret position
* *
* @return {Object} return * @return {Object} return
* @return {Integer} return.start 選択開始位置 * @return {Number} return.start start position for the selection
* @return {Integer} return.end 選択終了位置 * @return {Number} return.end end position for the selection
*/ */
case 'getPos': case 'getPos':
return _CaretOperation.getPos(this[0]); return _CaretOperation.getPos(this[0]);
break;
/** /**
* selection('setPos', opts) * selection('setPos', opts)
* キャレット位置を設定します * set caret position
* *
* @param {Integer} opts.start 選択開始位置 * @param {Number} opts.start start position for the selection
* @param {Integer} opts.end 選択終了位置 * @param {Number} opts.end end position for the selection
*/ */
case 'setPos': case 'setPos':
return this.each(function() { return this.each(function() {
_CaretOperation.setPos(this, opts); _CaretOperation.setPos(this, opts);
}); });
break;
/** /**
* selection('replace', opts) * selection('replace', opts)
* 選択文字列を置き換えます * replace the selected text
* *
* @param {String} opts.text 置き換える文字列 * @param {String} opts.text replacement text
* @param {String} opts.caret キャレットモード "keep" | "start" | "end" のいずれか * @param {String} opts.caret caret mode: any of the following: "keep" | "start" | "end"
*/ */
case 'replace': case 'replace':
return this.each(function() { return this.each(function() {
_CaretOperation.replace(this, opts.text, opts.caret); _CaretOperation.replace(this, opts.text, opts.caret);
}); });
break;
/** /**
* selection('insert', opts) * selection('insert', opts)
* 選択文字列の前もしくは後に文字列を挿入えます * insert before/after the selected text
* *
* @param {String} opts.text 挿入文字列 * @param {String} opts.text insertion text
* @param {String} opts.caret キャレットモード "keep" | "start" | "end" のいずれか * @param {String} opts.caret caret mode: any of the following: "keep" | "start" | "end"
* @param {String} opts.mode 挿入モード "before" | "after" のいずれか * @param {String} opts.mode insertion mode: any of the following: "before" | "after"
*/ */
case 'insert': case 'insert':
return this.each(function() { return this.each(function() {
if (opts.mode == 'before') { if (opts.mode === 'before') {
_CaretOperation.insertBefore(this, opts.text, opts.caret); _CaretOperation.insertBefore(this, opts.text, opts.caret);
} else { } else {
_CaretOperation.insertAfter(this, opts.text, opts.caret); _CaretOperation.insertAfter(this, opts.text, opts.caret);
} }
}); });
break;
/** /**
* selection('get') * selection('get')
* 選択されている文字列を取得 * get selected text
* *
* @return {String} return * @return {String} return
*/ */
case 'get': case 'get':
/* falls through */
default: default:
return _CaretOperation.getText(this[0]); return _CaretOperation.getText(this[0]);
break;
} }
return this; return this;

File diff suppressed because one or more lines are too long

View file

@ -8,6 +8,7 @@
<script src="{{ pathPrefix }}/static/vendor/rickshaw/vendor/d3.layout.min.js?v={{ buildVersion }}"></script> <script src="{{ pathPrefix }}/static/vendor/rickshaw/vendor/d3.layout.min.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/static/vendor/rickshaw/rickshaw.min.js?v={{ buildVersion }}"></script> <script src="{{ pathPrefix }}/static/vendor/rickshaw/rickshaw.min.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/static/vendor/moment/moment.min.js?v={{ buildVersion }}"></script> <script src="{{ pathPrefix }}/static/vendor/moment/moment.min.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/static/vendor/moment/moment-timezone-with-data.min.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/static/vendor/eonasdan-bootstrap-datetimepicker/bootstrap-datetimepicker.min.js?v={{ buildVersion }}"></script> <script src="{{ pathPrefix }}/static/vendor/eonasdan-bootstrap-datetimepicker/bootstrap-datetimepicker.min.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/static/vendor/bootstrap3-typeahead/bootstrap3-typeahead.min.js?v={{ buildVersion }}"></script> <script src="{{ pathPrefix }}/static/vendor/bootstrap3-typeahead/bootstrap3-typeahead.min.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/static/vendor/fuzzy/fuzzy.js?v={{ buildVersion }}"></script> <script src="{{ pathPrefix }}/static/vendor/fuzzy/fuzzy.js?v={{ buildVersion }}"></script>

View file

@ -284,6 +284,11 @@ func serveDebug(w http.ResponseWriter, req *http.Request) {
ctx := req.Context() ctx := req.Context()
subpath := route.Param(ctx, "subpath") subpath := route.Param(ctx, "subpath")
if subpath == "/pprof" {
http.Redirect(w, req, req.URL.Path+"/", http.StatusMovedPermanently)
return
}
if !strings.HasPrefix(subpath, "/pprof/") { if !strings.HasPrefix(subpath, "/pprof/") {
http.NotFound(w, req) http.NotFound(w, req)
return return
@ -300,6 +305,7 @@ func serveDebug(w http.ResponseWriter, req *http.Request) {
case "trace": case "trace":
pprof.Trace(w, req) pprof.Trace(w, req)
default: default:
req.URL.Path = "/debug/pprof/" + subpath
pprof.Index(w, req) pprof.Index(w, req)
} }
} }

View file

@ -16,6 +16,7 @@ package web
import ( import (
"context" "context"
"net/http" "net/http"
"net/http/httptest"
"net/url" "net/url"
"testing" "testing"
"time" "time"
@ -231,3 +232,37 @@ func TestRoutePrefix(t *testing.T) {
t.Fatalf("Path "+opts.RoutePrefix+"/version with server ready test, Expected status 200 got: %s", resp.Status) t.Fatalf("Path "+opts.RoutePrefix+"/version with server ready test, Expected status 200 got: %s", resp.Status)
} }
} }
func TestDebugHandler(t *testing.T) {
for _, tc := range []struct {
prefix, url string
code int
}{
{"/", "/debug/pprof/cmdline", 200},
{"/foo", "/foo/debug/pprof/cmdline", 200},
{"/", "/debug/pprof/goroutine", 200},
{"/foo", "/foo/debug/pprof/goroutine", 200},
{"/", "/debug/pprof/foo", 404},
{"/foo", "/bar/debug/pprof/goroutine", 404},
} {
opts := &Options{
RoutePrefix: tc.prefix,
MetricsPath: "/metrics",
}
handler := New(nil, opts)
handler.Ready()
w := httptest.NewRecorder()
req, err := http.NewRequest("GET", tc.url, nil)
if err != nil {
t.Fatalf("Unexpected error %s", err)
}
handler.router.ServeHTTP(w, req)
if w.Code != tc.code {
t.Fatalf("Unexpected status code %d: %s", w.Code, w.Body.String())
}
}
}