rebase: bump the k8s-dependencies group in /e2e with 3 updates

Bumps the k8s-dependencies group in /e2e with 3 updates: [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery), [k8s.io/cloud-provider](https://github.com/kubernetes/cloud-provider) and [k8s.io/pod-security-admission](https://github.com/kubernetes/pod-security-admission).


Updates `k8s.io/apimachinery` from 0.32.3 to 0.33.0
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.32.3...v0.33.0)

Updates `k8s.io/cloud-provider` from 0.32.3 to 0.33.0
- [Commits](https://github.com/kubernetes/cloud-provider/compare/v0.32.3...v0.33.0)

Updates `k8s.io/pod-security-admission` from 0.32.3 to 0.33.0
- [Commits](https://github.com/kubernetes/pod-security-admission/compare/v0.32.3...v0.33.0)

---
updated-dependencies:
- dependency-name: k8s.io/apimachinery
  dependency-version: 0.33.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: k8s-dependencies
- dependency-name: k8s.io/cloud-provider
  dependency-version: 0.33.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: k8s-dependencies
- dependency-name: k8s.io/pod-security-admission
  dependency-version: 0.33.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: k8s-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot] 2025-05-06 11:20:01 +00:00 committed by mergify[bot]
parent d52dc2c4ba
commit dd77e72800
359 changed files with 11145 additions and 18557 deletions

View File

@ -1,8 +1,7 @@
module github.com/ceph/ceph-csi/e2e module github.com/ceph/ceph-csi/e2e
go 1.23.1 go 1.23.1
toolchain go1.24.1
toolchain go1.23.4
// my own dependencies // my own dependencies
replace ( replace (
@ -16,12 +15,12 @@ require (
github.com/onsi/ginkgo/v2 v2.23.4 github.com/onsi/ginkgo/v2 v2.23.4
github.com/onsi/gomega v1.37.0 github.com/onsi/gomega v1.37.0
// when updating k8s.io modules, update the 'replace' section below too // when updating k8s.io modules, update the 'replace' section below too
k8s.io/api v0.32.3 k8s.io/api v0.33.0
k8s.io/apimachinery v0.32.3 k8s.io/apimachinery v0.33.0
k8s.io/client-go v12.0.0+incompatible k8s.io/client-go v12.0.0+incompatible
k8s.io/cloud-provider v0.32.3 k8s.io/cloud-provider v0.33.0
k8s.io/kubernetes v1.32.3 k8s.io/kubernetes v1.32.3
k8s.io/pod-security-admission v0.32.3 k8s.io/pod-security-admission v0.33.0
) )
replace ( replace (
@ -39,12 +38,11 @@ replace (
require github.com/ceph/ceph-csi v2.0.1+incompatible require github.com/ceph/ceph-csi v2.0.1+incompatible
require ( require (
cel.dev/expr v0.19.1 // indirect cel.dev/expr v0.20.0 // indirect
github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab // indirect github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect
@ -75,15 +73,14 @@ require (
github.com/golang/protobuf v1.5.4 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/google/btree v1.1.3 // indirect github.com/google/btree v1.1.3 // indirect
github.com/google/cadvisor v0.51.0 // indirect github.com/google/cadvisor v0.51.0 // indirect
github.com/google/cel-go v0.22.0 // indirect github.com/google/cel-go v0.23.2 // indirect
github.com/google/gnostic-models v0.6.9 // indirect github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
@ -112,19 +109,19 @@ require (
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
go.etcd.io/etcd/api/v3 v3.5.16 // indirect go.etcd.io/etcd/api/v3 v3.5.21 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.16 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.21 // indirect
go.etcd.io/etcd/client/v3 v3.5.16 // indirect go.etcd.io/etcd/client/v3 v3.5.21 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
go.opentelemetry.io/otel v1.34.0 // indirect go.opentelemetry.io/otel v1.34.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 // indirect
go.opentelemetry.io/otel/metric v1.34.0 // indirect go.opentelemetry.io/otel/metric v1.34.0 // indirect
go.opentelemetry.io/otel/sdk v1.34.0 // indirect go.opentelemetry.io/otel/sdk v1.34.0 // indirect
go.opentelemetry.io/otel/trace v1.34.0 // indirect go.opentelemetry.io/otel/trace v1.34.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.opentelemetry.io/proto/otlp v1.4.0 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect go.uber.org/zap v1.27.0 // indirect
@ -136,36 +133,37 @@ require (
golang.org/x/sys v0.32.0 // indirect golang.org/x/sys v0.32.0 // indirect
golang.org/x/term v0.31.0 // indirect golang.org/x/term v0.31.0 // indirect
golang.org/x/text v0.24.0 // indirect golang.org/x/text v0.24.0 // indirect
golang.org/x/time v0.8.0 // indirect golang.org/x/time v0.9.0 // indirect
golang.org/x/tools v0.31.0 // indirect golang.org/x/tools v0.31.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
google.golang.org/grpc v1.71.1 // indirect google.golang.org/grpc v1.72.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.32.1 // indirect k8s.io/apiextensions-apiserver v0.32.1 // indirect
k8s.io/apiserver v0.32.3 // indirect k8s.io/apiserver v0.33.0 // indirect
k8s.io/component-base v0.32.3 // indirect k8s.io/component-base v0.33.0 // indirect
k8s.io/component-helpers v0.32.3 // indirect k8s.io/component-helpers v0.33.0 // indirect
k8s.io/controller-manager v0.32.3 // indirect k8s.io/controller-manager v0.33.0 // indirect
k8s.io/cri-api v0.32.2 // indirect k8s.io/cri-api v0.32.2 // indirect
k8s.io/cri-client v0.0.0 // indirect k8s.io/cri-client v0.0.0 // indirect
k8s.io/csi-translation-lib v0.32.2 // indirect k8s.io/csi-translation-lib v0.32.2 // indirect
k8s.io/dynamic-resource-allocation v0.0.0 // indirect k8s.io/dynamic-resource-allocation v0.0.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kms v0.32.3 // indirect k8s.io/kms v0.33.0 // indirect
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
k8s.io/kube-scheduler v0.0.0 // indirect k8s.io/kube-scheduler v0.0.0 // indirect
k8s.io/kubectl v0.0.0 // indirect k8s.io/kubectl v0.0.0 // indirect
k8s.io/kubelet v0.32.2 // indirect k8s.io/kubelet v0.32.2 // indirect
k8s.io/mount-utils v0.32.2 // indirect k8s.io/mount-utils v0.32.2 // indirect
k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect
) )

View File

@ -1,5 +1,5 @@
cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= cel.dev/expr v0.20.0 h1:OunBvVCfvpWlt4dN7zg3FM6TDkzOePe1+foGJ9AXeeI=
cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab h1:UKkYhof1njT1/xq4SEg5z+VpTgjmNeHwPGRQl7takDI= github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab h1:UKkYhof1njT1/xq4SEg5z+VpTgjmNeHwPGRQl7takDI=
github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab/go.mod h1:3VYc5hodBMJ5+l/7J4xAyMeuM2PNuepvHlGs8yilUCA= github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab/go.mod h1:3VYc5hodBMJ5+l/7J4xAyMeuM2PNuepvHlGs8yilUCA=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
@ -10,8 +10,6 @@ github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
@ -81,16 +79,16 @@ github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/cadvisor v0.51.0 h1:BspqSPdZoLKrnvuZNOvM/KiJ/A+RdixwagN20n+2H8k= github.com/google/cadvisor v0.51.0 h1:BspqSPdZoLKrnvuZNOvM/KiJ/A+RdixwagN20n+2H8k=
github.com/google/cadvisor v0.51.0/go.mod h1:czGE/c/P/i0QFpVNKTFrIEzord9Y10YfpwuaSWXELc0= github.com/google/cadvisor v0.51.0/go.mod h1:czGE/c/P/i0QFpVNKTFrIEzord9Y10YfpwuaSWXELc0=
github.com/google/cel-go v0.22.0 h1:b3FJZxpiv1vTMo2/5RDUqAHPxkT8mmMfJIrq1llbf7g= github.com/google/cel-go v0.23.2 h1:UdEe3CvQh3Nv+E/j9r1Y//WO0K0cSyD7/y0bzyLIMI4=
github.com/google/cel-go v0.22.0/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8= github.com/google/cel-go v0.23.2/go.mod h1:52Pb6QsDbC5kvgxvZhiL9QX1oZEkcUF/ZqaPx1J5Wwo=
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@ -103,16 +101,16 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
@ -220,32 +218,32 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
go.etcd.io/etcd/api/v3 v3.5.16 h1:WvmyJVbjWqK4R1E+B12RRHz3bRGy9XVfh++MgbN+6n0= go.etcd.io/etcd/api/v3 v3.5.21 h1:A6O2/JDb3tvHhiIz3xf9nJ7REHvtEFJJ3veW3FbCnS8=
go.etcd.io/etcd/api/v3 v3.5.16/go.mod h1:1P4SlIP/VwkDmGo3OlOD7faPeP8KDIFhqvciH5EfN28= go.etcd.io/etcd/api/v3 v3.5.21/go.mod h1:c3aH5wcvXv/9dqIw2Y810LDXJfhSYdHQ0vxmP3CCHVY=
go.etcd.io/etcd/client/pkg/v3 v3.5.16 h1:ZgY48uH6UvB+/7R9Yf4x574uCO3jIx0TRDyetSfId3Q= go.etcd.io/etcd/client/pkg/v3 v3.5.21 h1:lPBu71Y7osQmzlflM9OfeIV2JlmpBjqBNlLtcoBqUTc=
go.etcd.io/etcd/client/pkg/v3 v3.5.16/go.mod h1:V8acl8pcEK0Y2g19YlOV9m9ssUe6MgiDSobSoaBAM0E= go.etcd.io/etcd/client/pkg/v3 v3.5.21/go.mod h1:BgqT/IXPjK9NkeSDjbzwsHySX3yIle2+ndz28nVsjUs=
go.etcd.io/etcd/client/v2 v2.305.16 h1:kQrn9o5czVNaukf2A2At43cE9ZtWauOtf9vRZuiKXow= go.etcd.io/etcd/client/v2 v2.305.21 h1:eLiFfexc2mE+pTLz9WwnoEsX5JTTpLCYVivKkmVXIRA=
go.etcd.io/etcd/client/v2 v2.305.16/go.mod h1:h9YxWCzcdvZENbfzBTFCnoNumr2ax3F19sKMqHFmXHE= go.etcd.io/etcd/client/v2 v2.305.21/go.mod h1:OKkn4hlYNf43hpjEM3Ke3aRdUkhSl8xjKjSf8eCq2J8=
go.etcd.io/etcd/client/v3 v3.5.16 h1:sSmVYOAHeC9doqi0gv7v86oY/BTld0SEFGaxsU9eRhE= go.etcd.io/etcd/client/v3 v3.5.21 h1:T6b1Ow6fNjOLOtM0xSoKNQt1ASPCLWrF9XMHcH9pEyY=
go.etcd.io/etcd/client/v3 v3.5.16/go.mod h1:X+rExSGkyqxvu276cr2OwPLBaeqFu1cIl4vmRjAD/50= go.etcd.io/etcd/client/v3 v3.5.21/go.mod h1:mFYy67IOqmbRf/kRUvsHixzo3iG+1OF2W2+jVIQRAnU=
go.etcd.io/etcd/pkg/v3 v3.5.16 h1:cnavs5WSPWeK4TYwPYfmcr3Joz9BH+TZ6qoUtz6/+mc= go.etcd.io/etcd/pkg/v3 v3.5.21 h1:jUItxeKyrDuVuWhdh0HtjUANwyuzcb7/FAeUfABmQsk=
go.etcd.io/etcd/pkg/v3 v3.5.16/go.mod h1:+lutCZHG5MBBFI/U4eYT5yL7sJfnexsoM20Y0t2uNuY= go.etcd.io/etcd/pkg/v3 v3.5.21/go.mod h1:wpZx8Egv1g4y+N7JAsqi2zoUiBIUWznLjqJbylDjWgU=
go.etcd.io/etcd/raft/v3 v3.5.16 h1:zBXA3ZUpYs1AwiLGPafYAKKl/CORn/uaxYDwlNwndAk= go.etcd.io/etcd/raft/v3 v3.5.21 h1:dOmE0mT55dIUsX77TKBLq+RgyumsQuYeiRQnW/ylugk=
go.etcd.io/etcd/raft/v3 v3.5.16/go.mod h1:P4UP14AxofMJ/54boWilabqqWoW9eLodl6I5GdGzazI= go.etcd.io/etcd/raft/v3 v3.5.21/go.mod h1:fmcuY5R2SNkklU4+fKVBQi2biVp5vafMrWUEj4TJ4Cs=
go.etcd.io/etcd/server/v3 v3.5.16 h1:d0/SAdJ3vVsZvF8IFVb1k8zqMZ+heGcNfft71ul9GWE= go.etcd.io/etcd/server/v3 v3.5.21 h1:9w0/k12majtgarGmlMVuhwXRI2ob3/d1Ik3X5TKo0yU=
go.etcd.io/etcd/server/v3 v3.5.16/go.mod h1:ynhyZZpdDp1Gq49jkUg5mfkDWZwXnn3eIqCqtJnrD/s= go.etcd.io/etcd/server/v3 v3.5.21/go.mod h1:G1mOzdwuzKT1VRL7SqRchli/qcFrtLBTAQ4lV20sXXo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 h1:5pojmb1U1AogINhN3SurB+zm/nIcusopeBNp42f45QM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
@ -254,8 +252,8 @@ go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg=
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@ -298,8 +296,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@ -312,12 +310,12 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ=
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro=
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0=
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -336,20 +334,20 @@ k8s.io/api v0.32.2 h1:bZrMLEkgizC24G9eViHGOPbW+aRo9duEISRIJKfdJuw=
k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y= k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y=
k8s.io/apiextensions-apiserver v0.32.2 h1:2YMk285jWMk2188V2AERy5yDwBYrjgWYggscghPCvV4= k8s.io/apiextensions-apiserver v0.32.2 h1:2YMk285jWMk2188V2AERy5yDwBYrjgWYggscghPCvV4=
k8s.io/apiextensions-apiserver v0.32.2/go.mod h1:GPwf8sph7YlJT3H6aKUWtd0E+oyShk/YHWQHf/OOgCA= k8s.io/apiextensions-apiserver v0.32.2/go.mod h1:GPwf8sph7YlJT3H6aKUWtd0E+oyShk/YHWQHf/OOgCA=
k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ=
k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/apiserver v0.32.3 h1:kOw2KBuHOA+wetX1MkmrxgBr648ksz653j26ESuWNY8= k8s.io/apiserver v0.33.0 h1:QqcM6c+qEEjkOODHppFXRiw/cE2zP85704YrQ9YaBbc=
k8s.io/apiserver v0.32.3/go.mod h1:q1x9B8E/WzShF49wh3ADOh6muSfpmFL0I2t+TG0Zdgc= k8s.io/apiserver v0.33.0/go.mod h1:EixYOit0YTxt8zrO2kBU7ixAtxFce9gKGq367nFmqI8=
k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA= k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA=
k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94= k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94=
k8s.io/cloud-provider v0.32.3 h1:WC7KhWrqXsU4b0E4tjS+nBectGiJbr1wuc1TpWXvtZM= k8s.io/cloud-provider v0.33.0 h1:nVU2Q9QK7O50yaNx+pE61oDPqflsSsKygN43f5js9+I=
k8s.io/cloud-provider v0.32.3/go.mod h1:/fwBfgRPuh16n8vLHT+PPT+Bc4LAEaJYj38opO2wsYY= k8s.io/cloud-provider v0.33.0/go.mod h1:2reyEBbsimZJKHF325vxLBD5fcJGNeJHeLjJ+jGM8Qg=
k8s.io/component-base v0.32.3 h1:98WJvvMs3QZ2LYHBzvltFSeJjEx7t5+8s71P7M74u8k= k8s.io/component-base v0.33.0 h1:Ot4PyJI+0JAD9covDhwLp9UNkUja209OzsJ4FzScBNk=
k8s.io/component-base v0.32.3/go.mod h1:LWi9cR+yPAv7cu2X9rZanTiFKB2kHA+JjmhkKjCZRpI= k8s.io/component-base v0.33.0/go.mod h1:aXYZLbw3kihdkOPMDhWbjGCO6sg+luw554KP51t8qCU=
k8s.io/component-helpers v0.32.3 h1:9veHpOGTPLluqU4hAu5IPOwkOIZiGAJUhHndfVc5FT4= k8s.io/component-helpers v0.33.0 h1:0AdW0A0mIgljLgtG0hJDdJl52PPqTrtMgOgtm/9i/Ys=
k8s.io/component-helpers v0.32.3/go.mod h1:utTBXk8lhkJewBKNuNf32Xl3KT/0VV19DmiXU/SV4Ao= k8s.io/component-helpers v0.33.0/go.mod h1:9SRiXfLldPw9lEEuSsapMtvT8j/h1JyFFapbtybwKvU=
k8s.io/controller-manager v0.32.3 h1:jBxZnQ24k6IMeWLyxWZmpa3QVS7ww+osAIzaUY/jqyc= k8s.io/controller-manager v0.33.0 h1:O9LnTjffOe62d66gMcKLuPXsBjY5sqETWEIzg+DVL8w=
k8s.io/controller-manager v0.32.3/go.mod h1:out1L3DZjE/p7JG0MoMMIaQGWIkt3c+pKaswqSHgKsI= k8s.io/controller-manager v0.33.0/go.mod h1:vQwAQnroav4+UyE2acW1Rj6CSsHPzr2/018kgRLYqlI=
k8s.io/cri-api v0.32.2 h1:7DuaOHpOcXweZeBUbRdK0iCroxctGp73VwgrA0u7kho= k8s.io/cri-api v0.32.2 h1:7DuaOHpOcXweZeBUbRdK0iCroxctGp73VwgrA0u7kho=
k8s.io/cri-api v0.32.2/go.mod h1:DCzMuTh2padoinefWME0G678Mc3QFbLMF2vEweGzBAI= k8s.io/cri-api v0.32.2/go.mod h1:DCzMuTh2padoinefWME0G678Mc3QFbLMF2vEweGzBAI=
k8s.io/cri-client v0.32.2 h1:vjowJUyu14IbmifqCKJHE9rK/BPSfkXvltqN42W1Zuo= k8s.io/cri-client v0.32.2 h1:vjowJUyu14IbmifqCKJHE9rK/BPSfkXvltqN42W1Zuo=
@ -360,10 +358,10 @@ k8s.io/dynamic-resource-allocation v0.32.2 h1:6wP8/GGvhhvTJLrzwPSoMJDnspmosFj1CK
k8s.io/dynamic-resource-allocation v0.32.2/go.mod h1:+3qnQfvikLHVZrdZ0/gYkRiV96weUR9j7+Ph3Ui/hYU= k8s.io/dynamic-resource-allocation v0.32.2/go.mod h1:+3qnQfvikLHVZrdZ0/gYkRiV96weUR9j7+Ph3Ui/hYU=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kms v0.32.3 h1:HhHw5+pRCzEJp3oFFJ1q5W2N6gAI7YkUg4ay4Z0dgwM= k8s.io/kms v0.33.0 h1:fhQSW/vyaWDhMp0vDuO/sLg2RlGZf4F77beSXcB4/eE=
k8s.io/kms v0.32.3/go.mod h1:Bk2evz/Yvk0oVrvm4MvZbgq8BD34Ksxs2SRHn4/UiOM= k8s.io/kms v0.33.0/go.mod h1:C1I8mjFFBNzfUZXYt9FZVJ8MJl7ynFbGgZFbBzkBJ3E=
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 h1:hcha5B1kVACrLujCKLbr8XWMxCxzQx42DY8QKYJrDLg= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7/go.mod h1:GewRfANuJ70iYzvn+i4lezLDAFzvjxZYK1gn1lWcfas= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
k8s.io/kube-scheduler v0.32.2 h1:vBm6iIjWaD10OPmtkt/503LTKvrN8dWVceeBcpKj/ns= k8s.io/kube-scheduler v0.32.2 h1:vBm6iIjWaD10OPmtkt/503LTKvrN8dWVceeBcpKj/ns=
k8s.io/kube-scheduler v0.32.2/go.mod h1:dD5yuYpnsCfgZmzvncUNPdvXGJXA1hw3gXq7DH3+aCQ= k8s.io/kube-scheduler v0.32.2/go.mod h1:dD5yuYpnsCfgZmzvncUNPdvXGJXA1hw3gXq7DH3+aCQ=
k8s.io/kubectl v0.32.2 h1:TAkag6+XfSBgkqK9I7ZvwtF0WVtUAvK8ZqTt+5zi1Us= k8s.io/kubectl v0.32.2 h1:TAkag6+XfSBgkqK9I7ZvwtF0WVtUAvK8ZqTt+5zi1Us=
@ -374,15 +372,18 @@ k8s.io/kubernetes v1.32.3 h1:2A58BlNME8NwsMawmnM6InYo3Jf35Nw5G79q46kXwoA=
k8s.io/kubernetes v1.32.3/go.mod h1:GvhiBeolvSRzBpFlgM0z/Bbu3Oxs9w3P6XfEgYaMi8k= k8s.io/kubernetes v1.32.3/go.mod h1:GvhiBeolvSRzBpFlgM0z/Bbu3Oxs9w3P6XfEgYaMi8k=
k8s.io/mount-utils v0.32.2 h1:aDwp+ucWiVnDr/LpRg88/dsXf/vm6gI1VZkYH3+3+Vw= k8s.io/mount-utils v0.32.2 h1:aDwp+ucWiVnDr/LpRg88/dsXf/vm6gI1VZkYH3+3+Vw=
k8s.io/mount-utils v0.32.2/go.mod h1:Kun5c2svjAPx0nnvJKYQWhfeNW+O0EpzHgRhDcYoSY0= k8s.io/mount-utils v0.32.2/go.mod h1:Kun5c2svjAPx0nnvJKYQWhfeNW+O0EpzHgRhDcYoSY0=
k8s.io/pod-security-admission v0.32.3 h1:scV0PQc3PdD6sXOMHukPZOCzGCGZeVN5z999gHBpkOc= k8s.io/pod-security-admission v0.33.0 h1:di/iicB5plCq+iQeqgf2s1N5DOSzTDiOOv5OiAbuYWE=
k8s.io/pod-security-admission v0.32.3/go.mod h1:K1saHV9cPicHSnQuavHxR1zohKhHMajbk8e0Z7pXAdc= k8s.io/pod-security-admission v0.33.0/go.mod h1:McuUMtSclLNxQdCkDTTWqKR79jnpHT/022GuanVU/Wg=
k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 h1:CPT0ExVicCzcpeN4baWEV2ko2Z/AsiZgEdwgcfwLgMo= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/structured-merge-diff/v4 v4.5.0 h1:nbCitCK2hfnhyiKo6uf2HxUPTCodY6Qaf85SbDIaMBk= sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.5.0/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

View File

@ -69,5 +69,3 @@ For more detail, see:
* [Language Definition](doc/langdef.md) * [Language Definition](doc/langdef.md)
Released under the [Apache License](LICENSE). Released under the [Apache License](LICENSE).
Disclaimer: This is not an official Google product.

View File

@ -1,14 +0,0 @@
language: go
go:
- 1.1
- 1.2
- 1.3
- 1.4
- 1.5
- 1.6
- tip
notifications:
email:
- bwatas@gmail.com

View File

@ -1,63 +0,0 @@
#### Support
If you do have a contribution to the package, feel free to create a Pull Request or an Issue.
#### What to contribute
If you don't know what to do, there are some features and functions that need to be done
- [ ] Refactor code
- [ ] Edit docs and [README](https://github.com/asaskevich/govalidator/README.md): spellcheck, grammar and typo check
- [ ] Create actual list of contributors and projects that currently using this package
- [ ] Resolve [issues and bugs](https://github.com/asaskevich/govalidator/issues)
- [ ] Update actual [list of functions](https://github.com/asaskevich/govalidator#list-of-functions)
- [ ] Update [list of validators](https://github.com/asaskevich/govalidator#validatestruct-2) that available for `ValidateStruct` and add new
- [ ] Implement new validators: `IsFQDN`, `IsIMEI`, `IsPostalCode`, `IsISIN`, `IsISRC` etc
- [ ] Implement [validation by maps](https://github.com/asaskevich/govalidator/issues/224)
- [ ] Implement fuzzing testing
- [ ] Implement some struct/map/array utilities
- [ ] Implement map/array validation
- [ ] Implement benchmarking
- [ ] Implement batch of examples
- [ ] Look at forks for new features and fixes
#### Advice
Feel free to create what you want, but keep in mind when you implement new features:
- Code must be clear and readable, names of variables/constants clearly describes what they are doing
- Public functions must be documented and described in source file and added to README.md to the list of available functions
- There are must be unit-tests for any new functions and improvements
## Financial contributions
We also welcome financial contributions in full transparency on our [open collective](https://opencollective.com/govalidator).
Anyone can file an expense. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed.
## Credits
### Contributors
Thank you to all the people who have already contributed to govalidator!
<a href="graphs/contributors"><img src="https://opencollective.com/govalidator/contributors.svg?width=890" /></a>
### Backers
Thank you to all our backers! [[Become a backer](https://opencollective.com/govalidator#backer)]
<a href="https://opencollective.com/govalidator#backers" target="_blank"><img src="https://opencollective.com/govalidator/backers.svg?width=890"></a>
### Sponsors
Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/govalidator#sponsor))
<a href="https://opencollective.com/govalidator/sponsor/0/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/1/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/2/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/3/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/4/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/5/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/6/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/7/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/8/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/9/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/9/avatar.svg"></a>

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,507 +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) [![Backers on Open Collective](https://opencollective.com/govalidator/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/govalidator/sponsors/badge.svg)](#sponsors) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fasaskevich%2Fgovalidator.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fasaskevich%2Fgovalidator?ref=badge_shield)
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.
`SetNilPtrAllowedByRequired` causes validation to pass when struct fields marked by `required` are set to nil. This is disabled by default for consistency, but some packages that need to be able to determine between `nil` and `zero value` state can use this. If disabled, both `nil` and `zero` values cause validation errors.
```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 IsISO693Alpha2(str string) bool
func IsISO693Alpha3b(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 IsRFC3339WithoutZone(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,
"rfc3339WithoutZone": IsRFC3339WithoutZone,
"ISO3166Alpha2": IsISO3166Alpha2,
"ISO3166Alpha3": IsISO3166Alpha3,
```
Validators with parameters
```go
"range(min|max)": Range,
"length(min|max)": ByteLength,
"runelength(min|max)": RuneLength,
"stringlength(min|max)": StringLength,
"matches(pattern)": StringMatches,
"in(string1|string2|...|stringN)": IsIn,
"rsapub(keylength)" : IsRsaPub,
```
And here is small example of usage:
```go
type Post struct {
Title string `valid:"alphanum,required"`
Message string `valid:"duck,ascii"`
Message2 string `valid:"animal(dog)"`
AuthorIP string `valid:"ipv4"`
Date string `valid:"-"`
}
post := &Post{
Title: "My Example Post",
Message: "duck",
Message2: "dog",
AuthorIP: "123.234.54.3",
}
// Add your own struct validation tags
govalidator.TagMap["duck"] = govalidator.Validator(func(str string) bool {
return str == "duck"
})
// Add your own struct validation tags with parameter
govalidator.ParamTagMap["animal"] = govalidator.ParamValidator(func(str string, params ...string) bool {
species := params[0]
return str == species
})
govalidator.ParamTagRegexMap["animal"] = regexp.MustCompile("^animal\\((\\w+)\\)$")
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
}))
```
###### Custom error messages
Custom error messages are supported via annotations by adding the `~` separator - here's an example of how to use it:
```go
type Ticket struct {
Id int64 `json:"id"`
FirstName string `json:"firstname" valid:"required~First name is blank"`
}
```
#### 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 to the package, feel free to create a Pull Request or an Issue.
#### What to contribute
If you don't know what to do, there are some features and functions that need to be done
- [ ] Refactor code
- [ ] Edit docs and [README](https://github.com/asaskevich/govalidator/README.md): spellcheck, grammar and typo check
- [ ] Create actual list of contributors and projects that currently using this package
- [ ] Resolve [issues and bugs](https://github.com/asaskevich/govalidator/issues)
- [ ] Update actual [list of functions](https://github.com/asaskevich/govalidator#list-of-functions)
- [ ] Update [list of validators](https://github.com/asaskevich/govalidator#validatestruct-2) that available for `ValidateStruct` and add new
- [ ] Implement new validators: `IsFQDN`, `IsIMEI`, `IsPostalCode`, `IsISIN`, `IsISRC` etc
- [ ] Implement [validation by maps](https://github.com/asaskevich/govalidator/issues/224)
- [ ] Implement fuzzing testing
- [ ] Implement some struct/map/array utilities
- [ ] Implement map/array validation
- [ ] Implement benchmarking
- [ ] Implement batch of examples
- [ ] Look at forks for new features and fixes
#### Advice
Feel free to create what you want, but keep in mind when you implement new features:
- Code must be clear and readable, names of variables/constants clearly describes what they are doing
- Public functions must be documented and described in source file and added to README.md to the list of available functions
- There are must be unit-tests for any new functions and improvements
## Credits
### Contributors
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
#### 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)
<a href="graphs/contributors"><img src="https://opencollective.com/govalidator/contributors.svg?width=890" /></a>
### Backers
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/govalidator#backer)]
<a href="https://opencollective.com/govalidator#backers" target="_blank"><img src="https://opencollective.com/govalidator/backers.svg?width=890"></a>
### Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/govalidator#sponsor)]
<a href="https://opencollective.com/govalidator/sponsor/0/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/1/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/2/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/3/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/4/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/5/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/6/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/7/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/8/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/9/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/9/avatar.svg"></a>
## License
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fasaskevich%2Fgovalidator.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fasaskevich%2Fgovalidator?ref=badge_large)

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,64 +0,0 @@
package govalidator
import (
"encoding/json"
"fmt"
"reflect"
"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 or any int type to an integer type 64, or 0 if the input is not an integer.
func ToInt(value interface{}) (res int64, err error) {
val := reflect.ValueOf(value)
switch value.(type) {
case int, int8, int16, int32, int64:
res = val.Int()
case uint, uint8, uint16, uint32, uint64:
res = int64(val.Uint())
case string:
if IsInt(val.String()) {
res, err = strconv.ParseInt(val.String(), 0, 64)
if err != nil {
res = 0
}
} else {
err = fmt.Errorf("math: square root of negative number %g", value)
res = 0
}
default:
err = fmt.Errorf("math: square root of negative number %g", value)
res = 0
}
return
}
// ToBoolean convert the input string to a boolean.
func ToBoolean(str string) (bool, error) {
return strconv.ParseBool(str)
}

View File

@ -1,43 +0,0 @@
package govalidator
import "strings"
// 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 errs []string
for _, e := range es {
errs = append(errs, e.Error())
}
return strings.Join(errs, ";")
}
// 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
// Validator indicates the name of the validator that failed
Validator string
Path []string
}
func (e Error) Error() string {
if e.CustomErrorMessageExists {
return e.Err.Error()
}
errName := e.Name
if len(e.Path) > 0 {
errName = strings.Join(append(e.Path, e.Name), ".")
}
return errName + ": " + e.Err.Error()
}

View File

@ -1,97 +0,0 @@
package govalidator
import (
"math"
"reflect"
)
// Abs returns absolute value of number
func Abs(value float64) float64 {
return math.Abs(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 InRangeInt(value, left, right interface{}) bool {
value64, _ := ToInt(value)
left64, _ := ToInt(left)
right64, _ := ToInt(right)
if left64 > right64 {
left64, right64 = right64, left64
}
return value64 >= left64 && value64 <= right64
}
// InRange returns true if value lies between left and right border
func InRangeFloat32(value, left, right float32) bool {
if left > right {
left, right = right, left
}
return value >= left && value <= right
}
// InRange returns true if value lies between left and right border
func InRangeFloat64(value, left, right float64) bool {
if left > right {
left, right = right, left
}
return value >= left && value <= right
}
// InRange returns true if value lies between left and right border, generic type to handle int, float32 or float64, all types must the same type
func InRange(value interface{}, left interface{}, right interface{}) bool {
reflectValue := reflect.TypeOf(value).Kind()
reflectLeft := reflect.TypeOf(left).Kind()
reflectRight := reflect.TypeOf(right).Kind()
if reflectValue == reflect.Int && reflectLeft == reflect.Int && reflectRight == reflect.Int {
return InRangeInt(value.(int), left.(int), right.(int))
} else if reflectValue == reflect.Float32 && reflectLeft == reflect.Float32 && reflectRight == reflect.Float32 {
return InRangeFloat32(value.(float32), left.(float32), right.(float32))
} else if reflectValue == reflect.Float64 && reflectLeft == reflect.Float64 && reflectRight == reflect.Float64 {
return InRangeFloat64(value.(float64), left.(float64), right.(float64))
} else {
return false
}
}
// IsWhole returns true if value is whole number
func IsWhole(value float64) bool {
return 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,101 +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*)?@)`
URLPath string = `((\/|\?|#)[^\s]*)`
URLPort string = `(:(\d{1,5}))`
URLIP string = `([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))`
URLSubdomain string = `((www\.)|([a-zA-Z0-9]+([-_\.]?[a-zA-Z0-9])*[a-zA-Z0-9]\.[a-zA-Z0-9]+))`
URL string = `^` + URLSchema + `?` + URLUsername + `?` + `((` + URLIP + `|(\[` + IP + `\])|(([a-zA-Z0-9]([a-zA-Z0-9-_]+)?[a-zA-Z0-9]([-\.][a-zA-Z0-9]+)*)|(` + URLSubdomain + `?))?(([a-zA-Z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-zA-Z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-zA-Z\x{00a1}-\x{ffff}]{1,}))?))\.?` + URLPort + `?` + URLPath + `?$`
SSN string = `^\d{3}[- ]?\d{2}[- ]?\d{4}$`
WinPath string = `^[a-zA-Z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$`
UnixPath string = `^(/[^/\x00]*)+/?$`
Semver string = "^v?(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(-(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\\+[0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)?$"
tagName string = "valid"
hasLowerCase string = ".*[[:lower:]]"
hasUpperCase string = ".*[[:upper:]]"
hasWhitespace string = ".*[[:space:]]"
hasWhitespaceOnly string = "^[[:space:]]+$"
)
// Used by IsFilePath func
const (
// Unknown is unresolved OS type
Unknown = iota
// Win is Windows type
Win
// Unix is *nix OS types
Unix
)
var (
userRegexp = regexp.MustCompile("^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+$")
hostRegexp = regexp.MustCompile("^[^\\s]+\\.[^\\s]+$")
userDotRegexp = regexp.MustCompile("(^[.]{1})|([.]{1}$)|([.]{2,})")
rxEmail = regexp.MustCompile(Email)
rxCreditCard = regexp.MustCompile(CreditCard)
rxISBN10 = regexp.MustCompile(ISBN10)
rxISBN13 = regexp.MustCompile(ISBN13)
rxUUID3 = regexp.MustCompile(UUID3)
rxUUID4 = regexp.MustCompile(UUID4)
rxUUID5 = regexp.MustCompile(UUID5)
rxUUID = regexp.MustCompile(UUID)
rxAlpha = regexp.MustCompile(Alpha)
rxAlphanumeric = regexp.MustCompile(Alphanumeric)
rxNumeric = regexp.MustCompile(Numeric)
rxInt = regexp.MustCompile(Int)
rxFloat = regexp.MustCompile(Float)
rxHexadecimal = regexp.MustCompile(Hexadecimal)
rxHexcolor = regexp.MustCompile(Hexcolor)
rxRGBcolor = regexp.MustCompile(RGBcolor)
rxASCII = regexp.MustCompile(ASCII)
rxPrintableASCII = regexp.MustCompile(PrintableASCII)
rxMultibyte = regexp.MustCompile(Multibyte)
rxFullWidth = regexp.MustCompile(FullWidth)
rxHalfWidth = regexp.MustCompile(HalfWidth)
rxBase64 = regexp.MustCompile(Base64)
rxDataURI = regexp.MustCompile(DataURI)
rxLatitude = regexp.MustCompile(Latitude)
rxLongitude = regexp.MustCompile(Longitude)
rxDNSName = regexp.MustCompile(DNSName)
rxURL = regexp.MustCompile(URL)
rxSSN = regexp.MustCompile(SSN)
rxWinPath = regexp.MustCompile(WinPath)
rxUnixPath = regexp.MustCompile(UnixPath)
rxSemver = regexp.MustCompile(Semver)
rxHasLowerCase = regexp.MustCompile(hasLowerCase)
rxHasUpperCase = regexp.MustCompile(hasUpperCase)
rxHasWhitespace = regexp.MustCompile(hasWhitespace)
rxHasWhitespaceOnly = regexp.MustCompile(hasWhitespaceOnly)
)

View File

@ -1,636 +0,0 @@
package govalidator
import (
"reflect"
"regexp"
"sort"
"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]tagOption
func (t tagOptionsMap) orderedKeys() []string {
var keys []string
for k := range t {
keys = append(keys, k)
}
sort.Slice(keys, func(a, b int) bool {
return t[keys[a]].order < t[keys[b]].order
})
return keys
}
type tagOption struct {
name string
customErrorMessage string
order int
}
// 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,
"rsapub": IsRsaPub,
}
// 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\((.+)\)$`),
"rsapub": regexp.MustCompile("^rsapub\\((\\d+)\\)$"),
}
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,
"rfc3339WithoutZone": IsRFC3339WithoutZone,
"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",
}
// ISO693Entry stores ISO language codes
type ISO693Entry struct {
Alpha3bCode string
Alpha2Code string
English string
}
//ISO693List based on http://data.okfn.org/data/core/language-codes/r/language-codes-3b2.json
var ISO693List = []ISO693Entry{
{Alpha3bCode: "aar", Alpha2Code: "aa", English: "Afar"},
{Alpha3bCode: "abk", Alpha2Code: "ab", English: "Abkhazian"},
{Alpha3bCode: "afr", Alpha2Code: "af", English: "Afrikaans"},
{Alpha3bCode: "aka", Alpha2Code: "ak", English: "Akan"},
{Alpha3bCode: "alb", Alpha2Code: "sq", English: "Albanian"},
{Alpha3bCode: "amh", Alpha2Code: "am", English: "Amharic"},
{Alpha3bCode: "ara", Alpha2Code: "ar", English: "Arabic"},
{Alpha3bCode: "arg", Alpha2Code: "an", English: "Aragonese"},
{Alpha3bCode: "arm", Alpha2Code: "hy", English: "Armenian"},
{Alpha3bCode: "asm", Alpha2Code: "as", English: "Assamese"},
{Alpha3bCode: "ava", Alpha2Code: "av", English: "Avaric"},
{Alpha3bCode: "ave", Alpha2Code: "ae", English: "Avestan"},
{Alpha3bCode: "aym", Alpha2Code: "ay", English: "Aymara"},
{Alpha3bCode: "aze", Alpha2Code: "az", English: "Azerbaijani"},
{Alpha3bCode: "bak", Alpha2Code: "ba", English: "Bashkir"},
{Alpha3bCode: "bam", Alpha2Code: "bm", English: "Bambara"},
{Alpha3bCode: "baq", Alpha2Code: "eu", English: "Basque"},
{Alpha3bCode: "bel", Alpha2Code: "be", English: "Belarusian"},
{Alpha3bCode: "ben", Alpha2Code: "bn", English: "Bengali"},
{Alpha3bCode: "bih", Alpha2Code: "bh", English: "Bihari languages"},
{Alpha3bCode: "bis", Alpha2Code: "bi", English: "Bislama"},
{Alpha3bCode: "bos", Alpha2Code: "bs", English: "Bosnian"},
{Alpha3bCode: "bre", Alpha2Code: "br", English: "Breton"},
{Alpha3bCode: "bul", Alpha2Code: "bg", English: "Bulgarian"},
{Alpha3bCode: "bur", Alpha2Code: "my", English: "Burmese"},
{Alpha3bCode: "cat", Alpha2Code: "ca", English: "Catalan; Valencian"},
{Alpha3bCode: "cha", Alpha2Code: "ch", English: "Chamorro"},
{Alpha3bCode: "che", Alpha2Code: "ce", English: "Chechen"},
{Alpha3bCode: "chi", Alpha2Code: "zh", English: "Chinese"},
{Alpha3bCode: "chu", Alpha2Code: "cu", English: "Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic"},
{Alpha3bCode: "chv", Alpha2Code: "cv", English: "Chuvash"},
{Alpha3bCode: "cor", Alpha2Code: "kw", English: "Cornish"},
{Alpha3bCode: "cos", Alpha2Code: "co", English: "Corsican"},
{Alpha3bCode: "cre", Alpha2Code: "cr", English: "Cree"},
{Alpha3bCode: "cze", Alpha2Code: "cs", English: "Czech"},
{Alpha3bCode: "dan", Alpha2Code: "da", English: "Danish"},
{Alpha3bCode: "div", Alpha2Code: "dv", English: "Divehi; Dhivehi; Maldivian"},
{Alpha3bCode: "dut", Alpha2Code: "nl", English: "Dutch; Flemish"},
{Alpha3bCode: "dzo", Alpha2Code: "dz", English: "Dzongkha"},
{Alpha3bCode: "eng", Alpha2Code: "en", English: "English"},
{Alpha3bCode: "epo", Alpha2Code: "eo", English: "Esperanto"},
{Alpha3bCode: "est", Alpha2Code: "et", English: "Estonian"},
{Alpha3bCode: "ewe", Alpha2Code: "ee", English: "Ewe"},
{Alpha3bCode: "fao", Alpha2Code: "fo", English: "Faroese"},
{Alpha3bCode: "fij", Alpha2Code: "fj", English: "Fijian"},
{Alpha3bCode: "fin", Alpha2Code: "fi", English: "Finnish"},
{Alpha3bCode: "fre", Alpha2Code: "fr", English: "French"},
{Alpha3bCode: "fry", Alpha2Code: "fy", English: "Western Frisian"},
{Alpha3bCode: "ful", Alpha2Code: "ff", English: "Fulah"},
{Alpha3bCode: "geo", Alpha2Code: "ka", English: "Georgian"},
{Alpha3bCode: "ger", Alpha2Code: "de", English: "German"},
{Alpha3bCode: "gla", Alpha2Code: "gd", English: "Gaelic; Scottish Gaelic"},
{Alpha3bCode: "gle", Alpha2Code: "ga", English: "Irish"},
{Alpha3bCode: "glg", Alpha2Code: "gl", English: "Galician"},
{Alpha3bCode: "glv", Alpha2Code: "gv", English: "Manx"},
{Alpha3bCode: "gre", Alpha2Code: "el", English: "Greek, Modern (1453-)"},
{Alpha3bCode: "grn", Alpha2Code: "gn", English: "Guarani"},
{Alpha3bCode: "guj", Alpha2Code: "gu", English: "Gujarati"},
{Alpha3bCode: "hat", Alpha2Code: "ht", English: "Haitian; Haitian Creole"},
{Alpha3bCode: "hau", Alpha2Code: "ha", English: "Hausa"},
{Alpha3bCode: "heb", Alpha2Code: "he", English: "Hebrew"},
{Alpha3bCode: "her", Alpha2Code: "hz", English: "Herero"},
{Alpha3bCode: "hin", Alpha2Code: "hi", English: "Hindi"},
{Alpha3bCode: "hmo", Alpha2Code: "ho", English: "Hiri Motu"},
{Alpha3bCode: "hrv", Alpha2Code: "hr", English: "Croatian"},
{Alpha3bCode: "hun", Alpha2Code: "hu", English: "Hungarian"},
{Alpha3bCode: "ibo", Alpha2Code: "ig", English: "Igbo"},
{Alpha3bCode: "ice", Alpha2Code: "is", English: "Icelandic"},
{Alpha3bCode: "ido", Alpha2Code: "io", English: "Ido"},
{Alpha3bCode: "iii", Alpha2Code: "ii", English: "Sichuan Yi; Nuosu"},
{Alpha3bCode: "iku", Alpha2Code: "iu", English: "Inuktitut"},
{Alpha3bCode: "ile", Alpha2Code: "ie", English: "Interlingue; Occidental"},
{Alpha3bCode: "ina", Alpha2Code: "ia", English: "Interlingua (International Auxiliary Language Association)"},
{Alpha3bCode: "ind", Alpha2Code: "id", English: "Indonesian"},
{Alpha3bCode: "ipk", Alpha2Code: "ik", English: "Inupiaq"},
{Alpha3bCode: "ita", Alpha2Code: "it", English: "Italian"},
{Alpha3bCode: "jav", Alpha2Code: "jv", English: "Javanese"},
{Alpha3bCode: "jpn", Alpha2Code: "ja", English: "Japanese"},
{Alpha3bCode: "kal", Alpha2Code: "kl", English: "Kalaallisut; Greenlandic"},
{Alpha3bCode: "kan", Alpha2Code: "kn", English: "Kannada"},
{Alpha3bCode: "kas", Alpha2Code: "ks", English: "Kashmiri"},
{Alpha3bCode: "kau", Alpha2Code: "kr", English: "Kanuri"},
{Alpha3bCode: "kaz", Alpha2Code: "kk", English: "Kazakh"},
{Alpha3bCode: "khm", Alpha2Code: "km", English: "Central Khmer"},
{Alpha3bCode: "kik", Alpha2Code: "ki", English: "Kikuyu; Gikuyu"},
{Alpha3bCode: "kin", Alpha2Code: "rw", English: "Kinyarwanda"},
{Alpha3bCode: "kir", Alpha2Code: "ky", English: "Kirghiz; Kyrgyz"},
{Alpha3bCode: "kom", Alpha2Code: "kv", English: "Komi"},
{Alpha3bCode: "kon", Alpha2Code: "kg", English: "Kongo"},
{Alpha3bCode: "kor", Alpha2Code: "ko", English: "Korean"},
{Alpha3bCode: "kua", Alpha2Code: "kj", English: "Kuanyama; Kwanyama"},
{Alpha3bCode: "kur", Alpha2Code: "ku", English: "Kurdish"},
{Alpha3bCode: "lao", Alpha2Code: "lo", English: "Lao"},
{Alpha3bCode: "lat", Alpha2Code: "la", English: "Latin"},
{Alpha3bCode: "lav", Alpha2Code: "lv", English: "Latvian"},
{Alpha3bCode: "lim", Alpha2Code: "li", English: "Limburgan; Limburger; Limburgish"},
{Alpha3bCode: "lin", Alpha2Code: "ln", English: "Lingala"},
{Alpha3bCode: "lit", Alpha2Code: "lt", English: "Lithuanian"},
{Alpha3bCode: "ltz", Alpha2Code: "lb", English: "Luxembourgish; Letzeburgesch"},
{Alpha3bCode: "lub", Alpha2Code: "lu", English: "Luba-Katanga"},
{Alpha3bCode: "lug", Alpha2Code: "lg", English: "Ganda"},
{Alpha3bCode: "mac", Alpha2Code: "mk", English: "Macedonian"},
{Alpha3bCode: "mah", Alpha2Code: "mh", English: "Marshallese"},
{Alpha3bCode: "mal", Alpha2Code: "ml", English: "Malayalam"},
{Alpha3bCode: "mao", Alpha2Code: "mi", English: "Maori"},
{Alpha3bCode: "mar", Alpha2Code: "mr", English: "Marathi"},
{Alpha3bCode: "may", Alpha2Code: "ms", English: "Malay"},
{Alpha3bCode: "mlg", Alpha2Code: "mg", English: "Malagasy"},
{Alpha3bCode: "mlt", Alpha2Code: "mt", English: "Maltese"},
{Alpha3bCode: "mon", Alpha2Code: "mn", English: "Mongolian"},
{Alpha3bCode: "nau", Alpha2Code: "na", English: "Nauru"},
{Alpha3bCode: "nav", Alpha2Code: "nv", English: "Navajo; Navaho"},
{Alpha3bCode: "nbl", Alpha2Code: "nr", English: "Ndebele, South; South Ndebele"},
{Alpha3bCode: "nde", Alpha2Code: "nd", English: "Ndebele, North; North Ndebele"},
{Alpha3bCode: "ndo", Alpha2Code: "ng", English: "Ndonga"},
{Alpha3bCode: "nep", Alpha2Code: "ne", English: "Nepali"},
{Alpha3bCode: "nno", Alpha2Code: "nn", English: "Norwegian Nynorsk; Nynorsk, Norwegian"},
{Alpha3bCode: "nob", Alpha2Code: "nb", English: "Bokmål, Norwegian; Norwegian Bokmål"},
{Alpha3bCode: "nor", Alpha2Code: "no", English: "Norwegian"},
{Alpha3bCode: "nya", Alpha2Code: "ny", English: "Chichewa; Chewa; Nyanja"},
{Alpha3bCode: "oci", Alpha2Code: "oc", English: "Occitan (post 1500); Provençal"},
{Alpha3bCode: "oji", Alpha2Code: "oj", English: "Ojibwa"},
{Alpha3bCode: "ori", Alpha2Code: "or", English: "Oriya"},
{Alpha3bCode: "orm", Alpha2Code: "om", English: "Oromo"},
{Alpha3bCode: "oss", Alpha2Code: "os", English: "Ossetian; Ossetic"},
{Alpha3bCode: "pan", Alpha2Code: "pa", English: "Panjabi; Punjabi"},
{Alpha3bCode: "per", Alpha2Code: "fa", English: "Persian"},
{Alpha3bCode: "pli", Alpha2Code: "pi", English: "Pali"},
{Alpha3bCode: "pol", Alpha2Code: "pl", English: "Polish"},
{Alpha3bCode: "por", Alpha2Code: "pt", English: "Portuguese"},
{Alpha3bCode: "pus", Alpha2Code: "ps", English: "Pushto; Pashto"},
{Alpha3bCode: "que", Alpha2Code: "qu", English: "Quechua"},
{Alpha3bCode: "roh", Alpha2Code: "rm", English: "Romansh"},
{Alpha3bCode: "rum", Alpha2Code: "ro", English: "Romanian; Moldavian; Moldovan"},
{Alpha3bCode: "run", Alpha2Code: "rn", English: "Rundi"},
{Alpha3bCode: "rus", Alpha2Code: "ru", English: "Russian"},
{Alpha3bCode: "sag", Alpha2Code: "sg", English: "Sango"},
{Alpha3bCode: "san", Alpha2Code: "sa", English: "Sanskrit"},
{Alpha3bCode: "sin", Alpha2Code: "si", English: "Sinhala; Sinhalese"},
{Alpha3bCode: "slo", Alpha2Code: "sk", English: "Slovak"},
{Alpha3bCode: "slv", Alpha2Code: "sl", English: "Slovenian"},
{Alpha3bCode: "sme", Alpha2Code: "se", English: "Northern Sami"},
{Alpha3bCode: "smo", Alpha2Code: "sm", English: "Samoan"},
{Alpha3bCode: "sna", Alpha2Code: "sn", English: "Shona"},
{Alpha3bCode: "snd", Alpha2Code: "sd", English: "Sindhi"},
{Alpha3bCode: "som", Alpha2Code: "so", English: "Somali"},
{Alpha3bCode: "sot", Alpha2Code: "st", English: "Sotho, Southern"},
{Alpha3bCode: "spa", Alpha2Code: "es", English: "Spanish; Castilian"},
{Alpha3bCode: "srd", Alpha2Code: "sc", English: "Sardinian"},
{Alpha3bCode: "srp", Alpha2Code: "sr", English: "Serbian"},
{Alpha3bCode: "ssw", Alpha2Code: "ss", English: "Swati"},
{Alpha3bCode: "sun", Alpha2Code: "su", English: "Sundanese"},
{Alpha3bCode: "swa", Alpha2Code: "sw", English: "Swahili"},
{Alpha3bCode: "swe", Alpha2Code: "sv", English: "Swedish"},
{Alpha3bCode: "tah", Alpha2Code: "ty", English: "Tahitian"},
{Alpha3bCode: "tam", Alpha2Code: "ta", English: "Tamil"},
{Alpha3bCode: "tat", Alpha2Code: "tt", English: "Tatar"},
{Alpha3bCode: "tel", Alpha2Code: "te", English: "Telugu"},
{Alpha3bCode: "tgk", Alpha2Code: "tg", English: "Tajik"},
{Alpha3bCode: "tgl", Alpha2Code: "tl", English: "Tagalog"},
{Alpha3bCode: "tha", Alpha2Code: "th", English: "Thai"},
{Alpha3bCode: "tib", Alpha2Code: "bo", English: "Tibetan"},
{Alpha3bCode: "tir", Alpha2Code: "ti", English: "Tigrinya"},
{Alpha3bCode: "ton", Alpha2Code: "to", English: "Tonga (Tonga Islands)"},
{Alpha3bCode: "tsn", Alpha2Code: "tn", English: "Tswana"},
{Alpha3bCode: "tso", Alpha2Code: "ts", English: "Tsonga"},
{Alpha3bCode: "tuk", Alpha2Code: "tk", English: "Turkmen"},
{Alpha3bCode: "tur", Alpha2Code: "tr", English: "Turkish"},
{Alpha3bCode: "twi", Alpha2Code: "tw", English: "Twi"},
{Alpha3bCode: "uig", Alpha2Code: "ug", English: "Uighur; Uyghur"},
{Alpha3bCode: "ukr", Alpha2Code: "uk", English: "Ukrainian"},
{Alpha3bCode: "urd", Alpha2Code: "ur", English: "Urdu"},
{Alpha3bCode: "uzb", Alpha2Code: "uz", English: "Uzbek"},
{Alpha3bCode: "ven", Alpha2Code: "ve", English: "Venda"},
{Alpha3bCode: "vie", Alpha2Code: "vi", English: "Vietnamese"},
{Alpha3bCode: "vol", Alpha2Code: "vo", English: "Volapük"},
{Alpha3bCode: "wel", Alpha2Code: "cy", English: "Welsh"},
{Alpha3bCode: "wln", Alpha2Code: "wa", English: "Walloon"},
{Alpha3bCode: "wol", Alpha2Code: "wo", English: "Wolof"},
{Alpha3bCode: "xho", Alpha2Code: "xh", English: "Xhosa"},
{Alpha3bCode: "yid", Alpha2Code: "yi", English: "Yiddish"},
{Alpha3bCode: "yor", Alpha2Code: "yo", English: "Yoruba"},
{Alpha3bCode: "zha", Alpha2Code: "za", English: "Zhuang; Chuang"},
{Alpha3bCode: "zul", Alpha2Code: "zu", English: "Zulu"},
}

View File

@ -1,270 +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 {
if chars == "" {
return strings.TrimLeftFunc(str, unicode.IsSpace)
}
r, _ := regexp.Compile("^[" + chars + "]+")
return r.ReplaceAllString(str, "")
}
// RightTrim trim characters from the right-side of the input.
// If second argument is empty, it's will be remove spaces.
func RightTrim(str, chars string) string {
if chars == "" {
return strings.TrimRightFunc(str, unicode.IsSpace)
}
r, _ := regexp.Compile("[" + chars + "]+$")
return r.ReplaceAllString(str, "")
}
// 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 r.ReplaceAllString(str, "")
}
// BlackList remove characters that appear in the blacklist.
func BlackList(str, chars string) string {
pattern := "[" + chars + "]+"
r, _ := regexp.Compile(pattern)
return r.ReplaceAllString(str, "")
}
// StripLow remove characters with a numerical value < 32 and 127, mostly control characters.
// 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 r.ReplaceAllString(str, 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 {
// not treat number as separate segment
if !unicode.IsLower(r) && string(r) != "_" && !unicode.IsNumber(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
}
// TruncatingErrorf removes extra args from fmt.Errorf if not formatted in the str object
func TruncatingErrorf(str string, args ...interface{}) error {
n := strings.Count(str, "%s")
return fmt.Errorf(str, args[:n]...)
}

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 ./...

View File

@ -217,7 +217,7 @@ func (e *Env) Check(ast *Ast) (*Ast, *Issues) {
chk, err := e.initChecker() chk, err := e.initChecker()
if err != nil { if err != nil {
errs := common.NewErrors(ast.Source()) errs := common.NewErrors(ast.Source())
errs.ReportError(common.NoLocation, err.Error()) errs.ReportErrorString(common.NoLocation, err.Error())
return nil, NewIssuesWithSourceInfo(errs, ast.NativeRep().SourceInfo()) return nil, NewIssuesWithSourceInfo(errs, ast.NativeRep().SourceInfo())
} }
@ -556,7 +556,8 @@ func (e *Env) PartialVars(vars any) (interpreter.PartialActivation, error) {
// TODO: Consider adding an option to generate a Program.Residual to avoid round-tripping to an // TODO: Consider adding an option to generate a Program.Residual to avoid round-tripping to an
// Ast format and then Program again. // Ast format and then Program again.
func (e *Env) ResidualAst(a *Ast, details *EvalDetails) (*Ast, error) { func (e *Env) ResidualAst(a *Ast, details *EvalDetails) (*Ast, error) {
pruned := interpreter.PruneAst(a.impl.Expr(), a.impl.SourceInfo().MacroCalls(), details.State()) ast := a.NativeRep()
pruned := interpreter.PruneAst(ast.Expr(), ast.SourceInfo().MacroCalls(), details.State())
newAST := &Ast{source: a.Source(), impl: pruned} newAST := &Ast{source: a.Source(), impl: pruned}
expr, err := AstToString(newAST) expr, err := AstToString(newAST)
if err != nil { if err != nil {
@ -582,7 +583,7 @@ func (e *Env) EstimateCost(ast *Ast, estimator checker.CostEstimator, opts ...ch
extendedOpts := make([]checker.CostOption, 0, len(e.costOptions)) extendedOpts := make([]checker.CostOption, 0, len(e.costOptions))
extendedOpts = append(extendedOpts, opts...) extendedOpts = append(extendedOpts, opts...)
extendedOpts = append(extendedOpts, e.costOptions...) extendedOpts = append(extendedOpts, e.costOptions...)
return checker.Cost(ast.impl, estimator, extendedOpts...) return checker.Cost(ast.NativeRep(), estimator, extendedOpts...)
} }
// configure applies a series of EnvOptions to the current environment. // configure applies a series of EnvOptions to the current environment.
@ -614,6 +615,9 @@ func (e *Env) configure(opts []EnvOption) (*Env, error) {
if e.HasFeature(featureVariadicLogicalASTs) { if e.HasFeature(featureVariadicLogicalASTs) {
prsrOpts = append(prsrOpts, parser.EnableVariadicOperatorASTs(true)) prsrOpts = append(prsrOpts, parser.EnableVariadicOperatorASTs(true))
} }
if e.HasFeature(featureIdentEscapeSyntax) {
prsrOpts = append(prsrOpts, parser.EnableIdentEscapeSyntax(true))
}
e.prsr, err = parser.NewParser(prsrOpts...) e.prsr, err = parser.NewParser(prsrOpts...)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -60,7 +60,7 @@ func NewInlineVariable(name string, definition *Ast) *InlineVariable {
// If the variable occurs more than once, the provided alias will be used to replace the expressions // If the variable occurs more than once, the provided alias will be used to replace the expressions
// where the variable name occurs. // where the variable name occurs.
func NewInlineVariableWithAlias(name, alias string, definition *Ast) *InlineVariable { func NewInlineVariableWithAlias(name, alias string, definition *Ast) *InlineVariable {
return &InlineVariable{name: name, alias: alias, def: definition.impl} return &InlineVariable{name: name, alias: alias, def: definition.NativeRep()}
} }
// NewInliningOptimizer creates and optimizer which replaces variables with expression definitions. // NewInliningOptimizer creates and optimizer which replaces variables with expression definitions.

View File

@ -62,7 +62,7 @@ func AstToCheckedExpr(a *Ast) (*exprpb.CheckedExpr, error) {
if !a.IsChecked() { if !a.IsChecked() {
return nil, fmt.Errorf("cannot convert unchecked ast") return nil, fmt.Errorf("cannot convert unchecked ast")
} }
return ast.ToProto(a.impl) return ast.ToProto(a.NativeRep())
} }
// ParsedExprToAst converts a parsed expression proto message to an Ast. // ParsedExprToAst converts a parsed expression proto message to an Ast.
@ -99,15 +99,17 @@ func AstToParsedExpr(a *Ast) (*exprpb.ParsedExpr, error) {
// Note, the conversion may not be an exact replica of the original expression, but will produce // Note, the conversion may not be an exact replica of the original expression, but will produce
// a string that is semantically equivalent and whose textual representation is stable. // a string that is semantically equivalent and whose textual representation is stable.
func AstToString(a *Ast) (string, error) { func AstToString(a *Ast) (string, error) {
return parser.Unparse(a.impl.Expr(), a.impl.SourceInfo()) return parser.Unparse(a.NativeRep().Expr(), a.NativeRep().SourceInfo())
} }
// RefValueToValue converts between ref.Val and api.expr.Value. // RefValueToValue converts between ref.Val and google.api.expr.v1alpha1.Value.
// The result Value is the serialized proto form. The ref.Val must not be error or unknown. // The result Value is the serialized proto form. The ref.Val must not be error or unknown.
func RefValueToValue(res ref.Val) (*exprpb.Value, error) { func RefValueToValue(res ref.Val) (*exprpb.Value, error) {
return ValueAsAlphaProto(res) return ValueAsAlphaProto(res)
} }
// ValueAsAlphaProto converts between ref.Val and google.api.expr.v1alpha1.Value.
// The result Value is the serialized proto form. The ref.Val must not be error or unknown.
func ValueAsAlphaProto(res ref.Val) (*exprpb.Value, error) { func ValueAsAlphaProto(res ref.Val) (*exprpb.Value, error) {
canonical, err := ValueAsProto(res) canonical, err := ValueAsProto(res)
if err != nil { if err != nil {
@ -118,6 +120,8 @@ func ValueAsAlphaProto(res ref.Val) (*exprpb.Value, error) {
return alpha, err return alpha, err
} }
// ValueAsProto converts between ref.Val and cel.expr.Value.
// The result Value is the serialized proto form. The ref.Val must not be error or unknown.
func ValueAsProto(res ref.Val) (*celpb.Value, error) { func ValueAsProto(res ref.Val) (*celpb.Value, error) {
switch res.Type() { switch res.Type() {
case types.BoolType: case types.BoolType:
@ -205,11 +209,12 @@ var (
anyPbType = reflect.TypeOf(&anypb.Any{}) anyPbType = reflect.TypeOf(&anypb.Any{})
) )
// ValueToRefValue converts between exprpb.Value and ref.Val. // ValueToRefValue converts between google.api.expr.v1alpha1.Value and ref.Val.
func ValueToRefValue(adapter types.Adapter, v *exprpb.Value) (ref.Val, error) { func ValueToRefValue(adapter types.Adapter, v *exprpb.Value) (ref.Val, error) {
return AlphaProtoAsValue(adapter, v) return AlphaProtoAsValue(adapter, v)
} }
// AlphaProtoAsValue converts between google.api.expr.v1alpha1.Value and ref.Val.
func AlphaProtoAsValue(adapter types.Adapter, v *exprpb.Value) (ref.Val, error) { func AlphaProtoAsValue(adapter types.Adapter, v *exprpb.Value) (ref.Val, error) {
canonical := &celpb.Value{} canonical := &celpb.Value{}
if err := convertProto(v, canonical); err != nil { if err := convertProto(v, canonical); err != nil {
@ -218,6 +223,7 @@ func AlphaProtoAsValue(adapter types.Adapter, v *exprpb.Value) (ref.Val, error)
return ProtoAsValue(adapter, canonical) return ProtoAsValue(adapter, canonical)
} }
// ProtoAsValue converts between cel.expr.Value and ref.Val.
func ProtoAsValue(adapter types.Adapter, v *celpb.Value) (ref.Val, error) { func ProtoAsValue(adapter types.Adapter, v *celpb.Value) (ref.Val, error) {
switch v.Kind.(type) { switch v.Kind.(type) {
case *celpb.Value_NullValue: case *celpb.Value_NullValue:

View File

@ -15,6 +15,7 @@
package cel package cel
import ( import (
"fmt"
"math" "math"
"strconv" "strconv"
"strings" "strings"
@ -35,9 +36,11 @@ const (
optMapMacro = "optMap" optMapMacro = "optMap"
optFlatMapMacro = "optFlatMap" optFlatMapMacro = "optFlatMap"
hasValueFunc = "hasValue" hasValueFunc = "hasValue"
unwrapOptFunc = "unwrapOpt"
optionalNoneFunc = "optional.none" optionalNoneFunc = "optional.none"
optionalOfFunc = "optional.of" optionalOfFunc = "optional.of"
optionalOfNonZeroValueFunc = "optional.ofNonZeroValue" optionalOfNonZeroValueFunc = "optional.ofNonZeroValue"
optionalUnwrapFunc = "optional.unwrap"
valueFunc = "value" valueFunc = "value"
unusedIterVar = "#unused" unusedIterVar = "#unused"
) )
@ -260,6 +263,37 @@ func (stdLibrary) ProgramOptions() []ProgramOption {
// be expressed with `optMap`. // be expressed with `optMap`.
// //
// msg.?elements.optFlatMap(e, e[?0]) // return the first element if present. // msg.?elements.optFlatMap(e, e[?0]) // return the first element if present.
// # First
//
// Introduced in version: 2
//
// Returns an optional with the first value from the right hand list, or
// optional.None.
//
// [1, 2, 3].first().value() == 1
// # Last
//
// Introduced in version: 2
//
// Returns an optional with the last value from the right hand list, or
// optional.None.
//
// [1, 2, 3].last().value() == 3
//
// This is syntactic sugar for msg.elements[msg.elements.size()-1].
// # Unwrap / UnwrapOpt
//
// Introduced in version: 2
//
// Returns a list of all the values that are not none in the input list of optional values.
// Can be used as optional.unwrap(List[T]) or with postfix notation: List[T].unwrapOpt()
//
// optional.unwrap([optional.of(42), optional.none()]) == [42]
// [optional.of(42), optional.none()].unwrapOpt() == [42]
func OptionalTypes(opts ...OptionalTypesOption) EnvOption { func OptionalTypes(opts ...OptionalTypesOption) EnvOption {
lib := &optionalLib{version: math.MaxUint32} lib := &optionalLib{version: math.MaxUint32}
for _, opt := range opts { for _, opt := range opts {
@ -303,6 +337,7 @@ func (lib *optionalLib) CompileOptions() []EnvOption {
optionalTypeV := OptionalType(paramTypeV) optionalTypeV := OptionalType(paramTypeV)
listTypeV := ListType(paramTypeV) listTypeV := ListType(paramTypeV)
mapTypeKV := MapType(paramTypeK, paramTypeV) mapTypeKV := MapType(paramTypeK, paramTypeV)
listOptionalTypeV := ListType(optionalTypeV)
opts := []EnvOption{ opts := []EnvOption{
// Enable the optional syntax in the parser. // Enable the optional syntax in the parser.
@ -375,6 +410,46 @@ func (lib *optionalLib) CompileOptions() []EnvOption {
if lib.version >= 1 { if lib.version >= 1 {
opts = append(opts, Macros(ReceiverMacro(optFlatMapMacro, 2, optFlatMap))) opts = append(opts, Macros(ReceiverMacro(optFlatMapMacro, 2, optFlatMap)))
} }
if lib.version >= 2 {
opts = append(opts, Function("last",
MemberOverload("list_last", []*Type{listTypeV}, optionalTypeV,
UnaryBinding(func(v ref.Val) ref.Val {
list := v.(traits.Lister)
sz := list.Size().Value().(int64)
if sz == 0 {
return types.OptionalNone
}
return types.OptionalOf(list.Get(types.Int(sz - 1)))
}),
),
))
opts = append(opts, Function("first",
MemberOverload("list_first", []*Type{listTypeV}, optionalTypeV,
UnaryBinding(func(v ref.Val) ref.Val {
list := v.(traits.Lister)
sz := list.Size().Value().(int64)
if sz == 0 {
return types.OptionalNone
}
return types.OptionalOf(list.Get(types.Int(0)))
}),
),
))
opts = append(opts, Function(optionalUnwrapFunc,
Overload("optional_unwrap", []*Type{listOptionalTypeV}, listTypeV,
UnaryBinding(optUnwrap))))
opts = append(opts, Function(unwrapOptFunc,
MemberOverload("optional_unwrapOpt", []*Type{listOptionalTypeV}, listTypeV,
UnaryBinding(optUnwrap))))
}
return opts return opts
} }
@ -439,6 +514,23 @@ func optFlatMap(meh MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Exp
), nil ), nil
} }
func optUnwrap(value ref.Val) ref.Val {
list := value.(traits.Lister)
var unwrappedList []ref.Val
iter := list.Iterator()
for iter.HasNext() == types.True {
val := iter.Next()
opt, isOpt := val.(*types.Optional)
if !isOpt {
return types.WrapErr(fmt.Errorf("value %v is not optional", val))
}
if opt.HasValue() {
unwrappedList = append(unwrappedList, opt.GetValue())
}
}
return types.DefaultTypeAdapter.NativeToValue(unwrappedList)
}
func enableOptionalSyntax() EnvOption { func enableOptionalSyntax() EnvOption {
return func(e *Env) (*Env, error) { return func(e *Env) (*Env, error) {
e.prsrOpts = append(e.prsrOpts, parser.EnableOptionalSyntax(true)) e.prsrOpts = append(e.prsrOpts, parser.EnableOptionalSyntax(true))
@ -677,7 +769,7 @@ var (
func timestampGetFullYear(ts, tz ref.Val) ref.Val { func timestampGetFullYear(ts, tz ref.Val) ref.Val {
t, err := inTimeZone(ts, tz) t, err := inTimeZone(ts, tz)
if err != nil { if err != nil {
return types.NewErr(err.Error()) return types.NewErrFromString(err.Error())
} }
return types.Int(t.Year()) return types.Int(t.Year())
} }
@ -685,7 +777,7 @@ func timestampGetFullYear(ts, tz ref.Val) ref.Val {
func timestampGetMonth(ts, tz ref.Val) ref.Val { func timestampGetMonth(ts, tz ref.Val) ref.Val {
t, err := inTimeZone(ts, tz) t, err := inTimeZone(ts, tz)
if err != nil { if err != nil {
return types.NewErr(err.Error()) return types.NewErrFromString(err.Error())
} }
// CEL spec indicates that the month should be 0-based, but the Time value // CEL spec indicates that the month should be 0-based, but the Time value
// for Month() is 1-based. // for Month() is 1-based.
@ -695,7 +787,7 @@ func timestampGetMonth(ts, tz ref.Val) ref.Val {
func timestampGetDayOfYear(ts, tz ref.Val) ref.Val { func timestampGetDayOfYear(ts, tz ref.Val) ref.Val {
t, err := inTimeZone(ts, tz) t, err := inTimeZone(ts, tz)
if err != nil { if err != nil {
return types.NewErr(err.Error()) return types.NewErrFromString(err.Error())
} }
return types.Int(t.YearDay() - 1) return types.Int(t.YearDay() - 1)
} }
@ -703,7 +795,7 @@ func timestampGetDayOfYear(ts, tz ref.Val) ref.Val {
func timestampGetDayOfMonthZeroBased(ts, tz ref.Val) ref.Val { func timestampGetDayOfMonthZeroBased(ts, tz ref.Val) ref.Val {
t, err := inTimeZone(ts, tz) t, err := inTimeZone(ts, tz)
if err != nil { if err != nil {
return types.NewErr(err.Error()) return types.NewErrFromString(err.Error())
} }
return types.Int(t.Day() - 1) return types.Int(t.Day() - 1)
} }
@ -711,7 +803,7 @@ func timestampGetDayOfMonthZeroBased(ts, tz ref.Val) ref.Val {
func timestampGetDayOfMonthOneBased(ts, tz ref.Val) ref.Val { func timestampGetDayOfMonthOneBased(ts, tz ref.Val) ref.Val {
t, err := inTimeZone(ts, tz) t, err := inTimeZone(ts, tz)
if err != nil { if err != nil {
return types.NewErr(err.Error()) return types.NewErrFromString(err.Error())
} }
return types.Int(t.Day()) return types.Int(t.Day())
} }
@ -719,7 +811,7 @@ func timestampGetDayOfMonthOneBased(ts, tz ref.Val) ref.Val {
func timestampGetDayOfWeek(ts, tz ref.Val) ref.Val { func timestampGetDayOfWeek(ts, tz ref.Val) ref.Val {
t, err := inTimeZone(ts, tz) t, err := inTimeZone(ts, tz)
if err != nil { if err != nil {
return types.NewErr(err.Error()) return types.NewErrFromString(err.Error())
} }
return types.Int(t.Weekday()) return types.Int(t.Weekday())
} }
@ -727,7 +819,7 @@ func timestampGetDayOfWeek(ts, tz ref.Val) ref.Val {
func timestampGetHours(ts, tz ref.Val) ref.Val { func timestampGetHours(ts, tz ref.Val) ref.Val {
t, err := inTimeZone(ts, tz) t, err := inTimeZone(ts, tz)
if err != nil { if err != nil {
return types.NewErr(err.Error()) return types.NewErrFromString(err.Error())
} }
return types.Int(t.Hour()) return types.Int(t.Hour())
} }
@ -735,7 +827,7 @@ func timestampGetHours(ts, tz ref.Val) ref.Val {
func timestampGetMinutes(ts, tz ref.Val) ref.Val { func timestampGetMinutes(ts, tz ref.Val) ref.Val {
t, err := inTimeZone(ts, tz) t, err := inTimeZone(ts, tz)
if err != nil { if err != nil {
return types.NewErr(err.Error()) return types.NewErrFromString(err.Error())
} }
return types.Int(t.Minute()) return types.Int(t.Minute())
} }
@ -743,7 +835,7 @@ func timestampGetMinutes(ts, tz ref.Val) ref.Val {
func timestampGetSeconds(ts, tz ref.Val) ref.Val { func timestampGetSeconds(ts, tz ref.Val) ref.Val {
t, err := inTimeZone(ts, tz) t, err := inTimeZone(ts, tz)
if err != nil { if err != nil {
return types.NewErr(err.Error()) return types.NewErrFromString(err.Error())
} }
return types.Int(t.Second()) return types.Int(t.Second())
} }
@ -751,7 +843,7 @@ func timestampGetSeconds(ts, tz ref.Val) ref.Val {
func timestampGetMilliseconds(ts, tz ref.Val) ref.Val { func timestampGetMilliseconds(ts, tz ref.Val) ref.Val {
t, err := inTimeZone(ts, tz) t, err := inTimeZone(ts, tz)
if err != nil { if err != nil {
return types.NewErr(err.Error()) return types.NewErrFromString(err.Error())
} }
return types.Int(t.Nanosecond() / 1000000) return types.Int(t.Nanosecond() / 1000000)
} }

View File

@ -48,8 +48,8 @@ func NewStaticOptimizer(optimizers ...ASTOptimizer) *StaticOptimizer {
// If issues are encountered, the Issues.Err() return value will be non-nil. // If issues are encountered, the Issues.Err() return value will be non-nil.
func (opt *StaticOptimizer) Optimize(env *Env, a *Ast) (*Ast, *Issues) { func (opt *StaticOptimizer) Optimize(env *Env, a *Ast) (*Ast, *Issues) {
// Make a copy of the AST to be optimized. // Make a copy of the AST to be optimized.
optimized := ast.Copy(a.impl) optimized := ast.Copy(a.NativeRep())
ids := newIDGenerator(ast.MaxID(a.impl)) ids := newIDGenerator(ast.MaxID(a.NativeRep()))
// Create the optimizer context, could be pooled in the future. // Create the optimizer context, could be pooled in the future.
issues := NewIssues(common.NewErrors(a.Source())) issues := NewIssues(common.NewErrors(a.Source()))
@ -86,7 +86,7 @@ func (opt *StaticOptimizer) Optimize(env *Env, a *Ast) (*Ast, *Issues) {
if iss.Err() != nil { if iss.Err() != nil {
return nil, iss return nil, iss
} }
optimized = checked.impl optimized = checked.NativeRep()
} }
// Return the optimized result. // Return the optimized result.

View File

@ -65,6 +65,9 @@ const (
// Enable error generation when a presence test or optional field selection is // Enable error generation when a presence test or optional field selection is
// performed on a primitive type. // performed on a primitive type.
featureEnableErrorOnBadPresenceTest featureEnableErrorOnBadPresenceTest
// Enable escape syntax for field identifiers (`).
featureIdentEscapeSyntax
) )
// EnvOption is a functional interface for configuring the environment. // EnvOption is a functional interface for configuring the environment.
@ -618,6 +621,12 @@ func EnableMacroCallTracking() EnvOption {
return features(featureEnableMacroCallTracking, true) return features(featureEnableMacroCallTracking, true)
} }
// EnableIdentifierEscapeSyntax enables identifier escaping (`) syntax for
// fields.
func EnableIdentifierEscapeSyntax() EnvOption {
return features(featureIdentEscapeSyntax, true)
}
// CrossTypeNumericComparisons makes it possible to compare across numeric types, e.g. double < int // CrossTypeNumericComparisons makes it possible to compare across numeric types, e.g. double < int
func CrossTypeNumericComparisons(enabled bool) EnvOption { func CrossTypeNumericComparisons(enabled bool) EnvOption {
return features(featureCrossTypeNumericComparisons, enabled) return features(featureCrossTypeNumericComparisons, enabled)
@ -655,6 +664,15 @@ func ParserExpressionSizeLimit(limit int) EnvOption {
} }
} }
// EnableHiddenAccumulatorName sets the parser to use the identifier '@result' for accumulators
// which is not normally accessible from CEL source.
func EnableHiddenAccumulatorName(enabled bool) EnvOption {
return func(e *Env) (*Env, error) {
e.prsrOpts = append(e.prsrOpts, parser.EnableHiddenAccumulatorName(enabled))
return e, nil
}
}
func maybeInteropProvider(provider any) (types.Provider, error) { func maybeInteropProvider(provider any) (types.Provider, error) {
switch p := provider.(type) { switch p := provider.(type) {
case types.Provider: case types.Provider:

View File

@ -100,6 +100,9 @@ type EvalDetails struct {
// State of the evaluation, non-nil if the OptTrackState or OptExhaustiveEval is specified // State of the evaluation, non-nil if the OptTrackState or OptExhaustiveEval is specified
// within EvalOptions. // within EvalOptions.
func (ed *EvalDetails) State() interpreter.EvalState { func (ed *EvalDetails) State() interpreter.EvalState {
if ed == nil {
return interpreter.NewEvalState()
}
return ed.state return ed.state
} }

View File

@ -529,9 +529,15 @@ func (c *checker) checkComprehension(e ast.Expr) {
c.isAssignable(types.DynType, rangeType) c.isAssignable(types.DynType, rangeType)
// Set the range iteration variable to type DYN as well. // Set the range iteration variable to type DYN as well.
varType = types.DynType varType = types.DynType
if comp.HasIterVar2() {
var2Type = types.DynType
}
default: default:
c.errors.notAComprehensionRange(comp.IterRange().ID(), c.location(comp.IterRange()), rangeType) c.errors.notAComprehensionRange(comp.IterRange().ID(), c.location(comp.IterRange()), rangeType)
varType = types.ErrorType varType = types.ErrorType
if comp.HasIterVar2() {
var2Type = types.ErrorType
}
} }
// Create a block scope for the loop. // Create a block scope for the loop.

View File

@ -28,15 +28,20 @@ import (
// CostEstimator estimates the sizes of variable length input data and the costs of functions. // CostEstimator estimates the sizes of variable length input data and the costs of functions.
type CostEstimator interface { type CostEstimator interface {
// EstimateSize returns a SizeEstimate for the given AstNode, or nil if // EstimateSize returns a SizeEstimate for the given AstNode, or nil if the estimator has no
// the estimator has no estimate to provide. The size is equivalent to the result of the CEL `size()` function: // estimate to provide.
// length of strings and bytes, number of map entries or number of list items. //
// EstimateSize is only called for AstNodes where // The size is equivalent to the result of the CEL `size()` function:
// CEL does not know the size; EstimateSize is not called for values defined inline in CEL where the size // * Number of unicode characters in a string
// is already obvious to CEL. // * Number of bytes in a sequence
// * Number of map entries or number of list items.
//
// EstimateSize is only called for AstNodes where CEL does not know the size; EstimateSize is not
// called for values defined inline in CEL where the size is already obvious to CEL.
EstimateSize(element AstNode) *SizeEstimate EstimateSize(element AstNode) *SizeEstimate
// EstimateCallCost returns the estimated cost of an invocation, or nil if
// the estimator has no estimate to provide. // EstimateCallCost returns the estimated cost of an invocation, or nil if the estimator has no
// estimate to provide.
EstimateCallCost(function, overloadID string, target *AstNode, args []AstNode) *CallEstimate EstimateCallCost(function, overloadID string, target *AstNode, args []AstNode) *CallEstimate
} }
@ -44,6 +49,7 @@ type CostEstimator interface {
// The ResultSize should only be provided if the call results in a map, list, string or bytes. // The ResultSize should only be provided if the call results in a map, list, string or bytes.
type CallEstimate struct { type CallEstimate struct {
CostEstimate CostEstimate
ResultSize *SizeEstimate ResultSize *SizeEstimate
} }
@ -53,10 +59,13 @@ type AstNode interface {
// represent type directly reachable from the provided type declarations. // represent type directly reachable from the provided type declarations.
// The first path element is a variable. All subsequent path elements are one of: field name, '@items', '@keys', '@values'. // The first path element is a variable. All subsequent path elements are one of: field name, '@items', '@keys', '@values'.
Path() []string Path() []string
// Type returns the deduced type of the AstNode. // Type returns the deduced type of the AstNode.
Type() *types.Type Type() *types.Type
// Expr returns the expression of the AstNode. // Expr returns the expression of the AstNode.
Expr() ast.Expr Expr() ast.Expr
// ComputedSize returns a size estimate of the AstNode derived from information available in the CEL expression. // ComputedSize returns a size estimate of the AstNode derived from information available in the CEL expression.
// For constants and inline list and map declarations, the exact size is returned. For concatenated list, strings // For constants and inline list and map declarations, the exact size is returned. For concatenated list, strings
// and bytes, the size is derived from the size estimates of the operands. nil is returned if there is no // and bytes, the size is derived from the size estimates of the operands. nil is returned if there is no
@ -84,43 +93,24 @@ func (e astNode) Expr() ast.Expr {
} }
func (e astNode) ComputedSize() *SizeEstimate { func (e astNode) ComputedSize() *SizeEstimate {
if e.derivedSize != nil {
return e.derivedSize return e.derivedSize
} }
var v uint64
switch e.expr.Kind() {
case ast.LiteralKind:
switch ck := e.expr.AsLiteral().(type) {
case types.String:
// converting to runes here is an O(n) operation, but
// this is consistent with how size is computed at runtime,
// and how the language definition defines string size
v = uint64(len([]rune(ck)))
case types.Bytes:
v = uint64(len(ck))
case types.Bool, types.Double, types.Duration,
types.Int, types.Timestamp, types.Uint,
types.Null:
v = uint64(1)
default:
return nil
}
case ast.ListKind:
v = uint64(e.expr.AsList().Size())
case ast.MapKind:
v = uint64(e.expr.AsMap().Size())
default:
return nil
}
return &SizeEstimate{Min: v, Max: v}
}
// SizeEstimate represents an estimated size of a variable length string, bytes, map or list. // SizeEstimate represents an estimated size of a variable length string, bytes, map or list.
type SizeEstimate struct { type SizeEstimate struct {
Min, Max uint64 Min, Max uint64
} }
// UnknownSizeEstimate returns a size between 0 and max uint
func UnknownSizeEstimate() SizeEstimate {
return unknownSizeEstimate
}
// FixedSizeEstimate returns a size estimate with a fixed min and max range.
func FixedSizeEstimate(size uint64) SizeEstimate {
return SizeEstimate{Min: size, Max: size}
}
// Add adds to another SizeEstimate and returns the sum. // Add adds to another SizeEstimate and returns the sum.
// If add would result in an uint64 overflow, the result is math.MaxUint64. // If add would result in an uint64 overflow, the result is math.MaxUint64.
func (se SizeEstimate) Add(sizeEstimate SizeEstimate) SizeEstimate { func (se SizeEstimate) Add(sizeEstimate SizeEstimate) SizeEstimate {
@ -175,12 +165,22 @@ type CostEstimate struct {
Min, Max uint64 Min, Max uint64
} }
// UnknownCostEstimate returns a cost with an unknown impact.
func UnknownCostEstimate() CostEstimate {
return unknownCostEstimate
}
// FixedCostEstimate returns a cost with a fixed min and max range.
func FixedCostEstimate(cost uint64) CostEstimate {
return CostEstimate{Min: cost, Max: cost}
}
// Add adds the costs and returns the sum. // Add adds the costs and returns the sum.
// If add would result in an uint64 overflow for the min or max, the value is set to math.MaxUint64. // If add would result in an uint64 overflow for the min or max, the value is set to math.MaxUint64.
func (ce CostEstimate) Add(cost CostEstimate) CostEstimate { func (ce CostEstimate) Add(cost CostEstimate) CostEstimate {
return CostEstimate{ return CostEstimate{
addUint64NoOverflow(ce.Min, cost.Min), Min: addUint64NoOverflow(ce.Min, cost.Min),
addUint64NoOverflow(ce.Max, cost.Max), Max: addUint64NoOverflow(ce.Max, cost.Max),
} }
} }
@ -188,8 +188,8 @@ func (ce CostEstimate) Add(cost CostEstimate) CostEstimate {
// If multiply would result in an uint64 overflow, the result is math.MaxUint64. // If multiply would result in an uint64 overflow, the result is math.MaxUint64.
func (ce CostEstimate) Multiply(cost CostEstimate) CostEstimate { func (ce CostEstimate) Multiply(cost CostEstimate) CostEstimate {
return CostEstimate{ return CostEstimate{
multiplyUint64NoOverflow(ce.Min, cost.Min), Min: multiplyUint64NoOverflow(ce.Min, cost.Min),
multiplyUint64NoOverflow(ce.Max, cost.Max), Max: multiplyUint64NoOverflow(ce.Max, cost.Max),
} }
} }
@ -197,8 +197,8 @@ func (ce CostEstimate) Multiply(cost CostEstimate) CostEstimate {
// nearest integer of the result, rounded up. // nearest integer of the result, rounded up.
func (ce CostEstimate) MultiplyByCostFactor(costPerUnit float64) CostEstimate { func (ce CostEstimate) MultiplyByCostFactor(costPerUnit float64) CostEstimate {
return CostEstimate{ return CostEstimate{
multiplyByCostFactor(ce.Min, costPerUnit), Min: multiplyByCostFactor(ce.Min, costPerUnit),
multiplyByCostFactor(ce.Max, costPerUnit), Max: multiplyByCostFactor(ce.Max, costPerUnit),
} }
} }
@ -245,49 +245,6 @@ func multiplyByCostFactor(x uint64, y float64) uint64 {
return uint64(ceil) return uint64(ceil)
} }
var (
selectAndIdentCost = CostEstimate{Min: common.SelectAndIdentCost, Max: common.SelectAndIdentCost}
constCost = CostEstimate{Min: common.ConstCost, Max: common.ConstCost}
createListBaseCost = CostEstimate{Min: common.ListCreateBaseCost, Max: common.ListCreateBaseCost}
createMapBaseCost = CostEstimate{Min: common.MapCreateBaseCost, Max: common.MapCreateBaseCost}
createMessageBaseCost = CostEstimate{Min: common.StructCreateBaseCost, Max: common.StructCreateBaseCost}
)
type coster struct {
// exprPath maps from Expr Id to field path.
exprPath map[int64][]string
// iterRanges tracks the iterRange of each iterVar.
iterRanges iterRangeScopes
// computedSizes tracks the computed sizes of call results.
computedSizes map[int64]SizeEstimate
checkedAST *ast.AST
estimator CostEstimator
overloadEstimators map[string]FunctionEstimator
// presenceTestCost will either be a zero or one based on whether has() macros count against cost computations.
presenceTestCost CostEstimate
}
// Use a stack of iterVar -> iterRange Expr Ids to handle shadowed variable names.
type iterRangeScopes map[string][]int64
func (vs iterRangeScopes) push(varName string, expr ast.Expr) {
vs[varName] = append(vs[varName], expr.ID())
}
func (vs iterRangeScopes) pop(varName string) {
varStack := vs[varName]
vs[varName] = varStack[:len(varStack)-1]
}
func (vs iterRangeScopes) peek(varName string) (int64, bool) {
varStack := vs[varName]
if len(varStack) > 0 {
return varStack[len(varStack)-1], true
}
return 0, false
}
// CostOption configures flags which affect cost computations. // CostOption configures flags which affect cost computations.
type CostOption func(*coster) error type CostOption func(*coster) error
@ -300,7 +257,7 @@ func PresenceTestHasCost(hasCost bool) CostOption {
c.presenceTestCost = selectAndIdentCost c.presenceTestCost = selectAndIdentCost
return nil return nil
} }
c.presenceTestCost = CostEstimate{Min: 0, Max: 0} c.presenceTestCost = FixedCostEstimate(0)
return nil return nil
} }
} }
@ -325,10 +282,11 @@ func Cost(checked *ast.AST, estimator CostEstimator, opts ...CostOption) (CostEs
checkedAST: checked, checkedAST: checked,
estimator: estimator, estimator: estimator,
overloadEstimators: map[string]FunctionEstimator{}, overloadEstimators: map[string]FunctionEstimator{},
exprPath: map[int64][]string{}, exprPaths: map[int64][]string{},
iterRanges: map[string][]int64{}, localVars: make(scopes),
computedSizes: map[int64]SizeEstimate{}, computedSizes: map[int64]SizeEstimate{},
presenceTestCost: CostEstimate{Min: 1, Max: 1}, computedEntrySizes: map[int64]entrySizeEstimate{},
presenceTestCost: FixedCostEstimate(1),
} }
for _, opt := range opts { for _, opt := range opts {
err := opt(c) err := opt(c)
@ -339,6 +297,165 @@ func Cost(checked *ast.AST, estimator CostEstimator, opts ...CostOption) (CostEs
return c.cost(checked.Expr()), nil return c.cost(checked.Expr()), nil
} }
type coster struct {
// exprPaths maps from Expr Id to field path.
exprPaths map[int64][]string
// localVars tracks the local and iteration variables assigned during evaluation.
localVars scopes
// computedSizes tracks the computed sizes of call results.
computedSizes map[int64]SizeEstimate
// computedEntrySizes tracks the size of list and map entries
computedEntrySizes map[int64]entrySizeEstimate
checkedAST *ast.AST
estimator CostEstimator
overloadEstimators map[string]FunctionEstimator
// presenceTestCost will either be a zero or one based on whether has() macros count against cost computations.
presenceTestCost CostEstimate
}
// entrySizeEstimate captures the container kind and associated key/index and value SizeEstimate values.
//
// An entrySizeEstimate only exists if both the key/index and the value have SizeEstimate values, otherwise
// a nil entrySizeEstimate should be used.
type entrySizeEstimate struct {
containerKind types.Kind
key SizeEstimate
val SizeEstimate
}
// container returns the container kind (list or map) of the entry.
func (s *entrySizeEstimate) container() types.Kind {
if s == nil {
return types.UnknownKind
}
return s.containerKind
}
// keySize returns the SizeEstimate for the key if one exists.
func (s *entrySizeEstimate) keySize() *SizeEstimate {
if s == nil {
return nil
}
return &s.key
}
// valSize returns the SizeEstimate for the value if one exists.
func (s *entrySizeEstimate) valSize() *SizeEstimate {
if s == nil {
return nil
}
return &s.val
}
func (s *entrySizeEstimate) union(other *entrySizeEstimate) *entrySizeEstimate {
if s == nil || other == nil {
return nil
}
sk := s.key.Union(other.key)
sv := s.val.Union(other.val)
return &entrySizeEstimate{
containerKind: s.containerKind,
key: sk,
val: sv,
}
}
// localVar captures the local variable size and entrySize estimates if they exist for variables
type localVar struct {
exprID int64
path []string
size *SizeEstimate
entrySize *entrySizeEstimate
}
// scopes is a stack of variable name to integer id stack to handle scopes created by cel.bind() like macros
type scopes map[string][]*localVar
func (s scopes) push(varName string, expr ast.Expr, path []string, size *SizeEstimate, entrySize *entrySizeEstimate) {
s[varName] = append(s[varName], &localVar{
exprID: expr.ID(),
path: path,
size: size,
entrySize: entrySize,
})
}
func (s scopes) pop(varName string) {
varStack := s[varName]
s[varName] = varStack[:len(varStack)-1]
}
func (s scopes) peek(varName string) (*localVar, bool) {
varStack := s[varName]
if len(varStack) > 0 {
return varStack[len(varStack)-1], true
}
return nil, false
}
func (c *coster) pushIterKey(varName string, rangeExpr ast.Expr) {
entrySize := c.computeEntrySize(rangeExpr)
size := entrySize.keySize()
path := c.getPath(rangeExpr)
container := entrySize.container()
if container == types.UnknownKind {
container = c.getType(rangeExpr).Kind()
}
subpath := "@keys"
if container == types.ListKind {
subpath = "@indices"
}
c.localVars.push(varName, rangeExpr, append(path, subpath), size, nil)
}
func (c *coster) pushIterValue(varName string, rangeExpr ast.Expr) {
entrySize := c.computeEntrySize(rangeExpr)
size := entrySize.valSize()
path := c.getPath(rangeExpr)
container := entrySize.container()
if container == types.UnknownKind {
container = c.getType(rangeExpr).Kind()
}
subpath := "@values"
if container == types.ListKind {
subpath = "@items"
}
c.localVars.push(varName, rangeExpr, append(path, subpath), size, nil)
}
func (c *coster) pushIterSingle(varName string, rangeExpr ast.Expr) {
entrySize := c.computeEntrySize(rangeExpr)
size := entrySize.keySize()
subpath := "@keys"
container := entrySize.container()
if container == types.UnknownKind {
container = c.getType(rangeExpr).Kind()
}
if container == types.ListKind {
size = entrySize.valSize()
subpath = "@items"
}
path := c.getPath(rangeExpr)
c.localVars.push(varName, rangeExpr, append(path, subpath), size, nil)
}
func (c *coster) pushLocalVar(varName string, e ast.Expr) {
path := c.getPath(e)
// note: retrieve the entry size for the local variable based on the size of the binding expression
// since the binding expression could be a list or map, the entry size should also be propagated
entrySize := c.computeEntrySize(e)
c.localVars.push(varName, e, path, c.computeSize(e), entrySize)
}
func (c *coster) peekLocalVar(varName string) (*localVar, bool) {
return c.localVars.peek(varName)
}
func (c *coster) popLocalVar(varName string) {
c.localVars.pop(varName)
}
func (c *coster) cost(e ast.Expr) CostEstimate { func (c *coster) cost(e ast.Expr) CostEstimate {
if e == nil { if e == nil {
return CostEstimate{} return CostEstimate{}
@ -360,7 +477,11 @@ func (c *coster) cost(e ast.Expr) CostEstimate {
case ast.StructKind: case ast.StructKind:
cost = c.costCreateStruct(e) cost = c.costCreateStruct(e)
case ast.ComprehensionKind: case ast.ComprehensionKind:
if c.isBind(e) {
cost = c.costBind(e)
} else {
cost = c.costComprehension(e) cost = c.costComprehension(e)
}
default: default:
return CostEstimate{} return CostEstimate{}
} }
@ -370,17 +491,11 @@ func (c *coster) cost(e ast.Expr) CostEstimate {
func (c *coster) costIdent(e ast.Expr) CostEstimate { func (c *coster) costIdent(e ast.Expr) CostEstimate {
identName := e.AsIdent() identName := e.AsIdent()
// build and track the field path // build and track the field path
if iterRange, ok := c.iterRanges.peek(identName); ok { if v, ok := c.peekLocalVar(identName); ok {
switch c.checkedAST.GetType(iterRange).Kind() { c.addPath(e, v.path)
case types.ListKind:
c.addPath(e, append(c.exprPath[iterRange], "@items"))
case types.MapKind:
c.addPath(e, append(c.exprPath[iterRange], "@keys"))
}
} else { } else {
c.addPath(e, []string{identName}) c.addPath(e, []string{identName})
} }
return selectAndIdentCost return selectAndIdentCost
} }
@ -405,14 +520,18 @@ func (c *coster) costSelect(e ast.Expr) CostEstimate {
// build and track the field path // build and track the field path
c.addPath(e, append(c.getPath(sel.Operand()), sel.FieldName())) c.addPath(e, append(c.getPath(sel.Operand()), sel.FieldName()))
return sum return sum
} }
func (c *coster) costCall(e ast.Expr) CostEstimate { func (c *coster) costCall(e ast.Expr) CostEstimate {
// Dyn is just a way to disable type-checking, so return the cost of 1 with the cost of the argument
if dynEstimate := c.maybeUnwrapDynCall(e); dynEstimate != nil {
return *dynEstimate
}
// Continue estimating the cost of all other calls.
call := e.AsCall() call := e.AsCall()
args := call.Args() args := call.Args()
var sum CostEstimate var sum CostEstimate
argTypes := make([]AstNode, len(args)) argTypes := make([]AstNode, len(args))
@ -435,7 +554,7 @@ func (c *coster) costCall(e ast.Expr) CostEstimate {
fnCost := CostEstimate{Min: uint64(math.MaxUint64), Max: 0} fnCost := CostEstimate{Min: uint64(math.MaxUint64), Max: 0}
var resultSize *SizeEstimate var resultSize *SizeEstimate
for _, overload := range overloadIDs { for _, overload := range overloadIDs {
overloadCost := c.functionCost(call.FunctionName(), overload, &targetType, argTypes, argCosts) overloadCost := c.functionCost(e, call.FunctionName(), overload, &targetType, argTypes, argCosts)
fnCost = fnCost.Union(overloadCost.CostEstimate) fnCost = fnCost.Union(overloadCost.CostEstimate)
if overloadCost.ResultSize != nil { if overloadCost.ResultSize != nil {
if resultSize == nil { if resultSize == nil {
@ -449,37 +568,73 @@ func (c *coster) costCall(e ast.Expr) CostEstimate {
switch overload { switch overload {
case overloads.IndexList: case overloads.IndexList:
if len(args) > 0 { if len(args) > 0 {
// note: assigning resultSize here could be redundant with the path-based lookup later
resultSize = c.computeEntrySize(args[0]).valSize()
c.addPath(e, append(c.getPath(args[0]), "@items")) c.addPath(e, append(c.getPath(args[0]), "@items"))
} }
case overloads.IndexMap: case overloads.IndexMap:
if len(args) > 0 { if len(args) > 0 {
resultSize = c.computeEntrySize(args[0]).valSize()
c.addPath(e, append(c.getPath(args[0]), "@values")) c.addPath(e, append(c.getPath(args[0]), "@values"))
} }
} }
if resultSize == nil {
resultSize = c.computeSize(e)
} }
if resultSize != nil {
c.computedSizes[e.ID()] = *resultSize
} }
c.setSize(e, resultSize)
return sum.Add(fnCost) return sum.Add(fnCost)
} }
func (c *coster) maybeUnwrapDynCall(e ast.Expr) *CostEstimate {
call := e.AsCall()
if call.FunctionName() != "dyn" {
return nil
}
arg := call.Args()[0]
argCost := c.cost(arg)
c.copySizeEstimates(e, arg)
callCost := FixedCostEstimate(1).Add(argCost)
return &callCost
}
func (c *coster) costCreateList(e ast.Expr) CostEstimate { func (c *coster) costCreateList(e ast.Expr) CostEstimate {
create := e.AsList() create := e.AsList()
var sum CostEstimate var sum CostEstimate
itemSize := SizeEstimate{Min: math.MaxUint64, Max: 0}
if create.Size() == 0 {
itemSize.Min = 0
}
for _, e := range create.Elements() { for _, e := range create.Elements() {
sum = sum.Add(c.cost(e)) sum = sum.Add(c.cost(e))
is := c.sizeOrUnknown(e)
itemSize = itemSize.Union(is)
} }
c.setEntrySize(e, &entrySizeEstimate{containerKind: types.ListKind, key: FixedSizeEstimate(1), val: itemSize})
return sum.Add(createListBaseCost) return sum.Add(createListBaseCost)
} }
func (c *coster) costCreateMap(e ast.Expr) CostEstimate { func (c *coster) costCreateMap(e ast.Expr) CostEstimate {
mapVal := e.AsMap() mapVal := e.AsMap()
var sum CostEstimate var sum CostEstimate
keySize := SizeEstimate{Min: math.MaxUint64, Max: 0}
valSize := SizeEstimate{Min: math.MaxUint64, Max: 0}
if mapVal.Size() == 0 {
valSize.Min = 0
keySize.Min = 0
}
for _, ent := range mapVal.Entries() { for _, ent := range mapVal.Entries() {
entry := ent.AsMapEntry() entry := ent.AsMapEntry()
sum = sum.Add(c.cost(entry.Key())) sum = sum.Add(c.cost(entry.Key()))
sum = sum.Add(c.cost(entry.Value())) sum = sum.Add(c.cost(entry.Value()))
// Compute the key size range
ks := c.sizeOrUnknown(entry.Key())
keySize = keySize.Union(ks)
// Compute the value size range
vs := c.sizeOrUnknown(entry.Value())
valSize = valSize.Union(vs)
} }
c.setEntrySize(e, &entrySizeEstimate{containerKind: types.MapKind, key: keySize, val: valSize})
return sum.Add(createMapBaseCost) return sum.Add(createMapBaseCost)
} }
@ -498,43 +653,76 @@ func (c *coster) costComprehension(e ast.Expr) CostEstimate {
var sum CostEstimate var sum CostEstimate
sum = sum.Add(c.cost(comp.IterRange())) sum = sum.Add(c.cost(comp.IterRange()))
sum = sum.Add(c.cost(comp.AccuInit())) sum = sum.Add(c.cost(comp.AccuInit()))
c.pushLocalVar(comp.AccuVar(), comp.AccuInit())
// Track the iterRange of each IterVar for field path construction // Track the iterRange of each IterVar and AccuVar for field path construction
c.iterRanges.push(comp.IterVar(), comp.IterRange()) if comp.HasIterVar2() {
c.pushIterKey(comp.IterVar(), comp.IterRange())
c.pushIterValue(comp.IterVar2(), comp.IterRange())
} else {
c.pushIterSingle(comp.IterVar(), comp.IterRange())
}
// Determine the cost for each element in the loop
loopCost := c.cost(comp.LoopCondition()) loopCost := c.cost(comp.LoopCondition())
stepCost := c.cost(comp.LoopStep()) stepCost := c.cost(comp.LoopStep())
c.iterRanges.pop(comp.IterVar())
// Clear the intermediate variable tracking.
c.popLocalVar(comp.IterVar())
if comp.HasIterVar2() {
c.popLocalVar(comp.IterVar2())
}
// Determine the result cost.
sum = sum.Add(c.cost(comp.Result())) sum = sum.Add(c.cost(comp.Result()))
rangeCnt := c.sizeEstimate(c.newAstNode(comp.IterRange())) c.localVars.pop(comp.AccuVar())
c.computedSizes[e.ID()] = rangeCnt
// Estimate the cost of the loop.
rangeCnt := c.sizeOrUnknown(comp.IterRange())
rangeCost := rangeCnt.MultiplyByCost(stepCost.Add(loopCost)) rangeCost := rangeCnt.MultiplyByCost(stepCost.Add(loopCost))
sum = sum.Add(rangeCost) sum = sum.Add(rangeCost)
switch k := comp.AccuInit().Kind(); k {
case ast.LiteralKind:
c.setSize(e, c.computeSize(comp.AccuInit()))
case ast.ListKind, ast.MapKind:
c.setSize(e, &rangeCnt)
// For a step which produces a container value, it will have an entry size associated
// with its expression id.
if stepEntrySize := c.computeEntrySize(comp.LoopStep()); stepEntrySize != nil {
c.setEntrySize(e, stepEntrySize)
break
}
}
return sum return sum
} }
func (c *coster) sizeEstimate(t AstNode) SizeEstimate { func (c *coster) isBind(e ast.Expr) bool {
if l := t.ComputedSize(); l != nil { comp := e.AsComprehension()
return *l iterRange := comp.IterRange()
} loopCond := comp.LoopCondition()
if l := c.estimator.EstimateSize(t); l != nil { return iterRange.Kind() == ast.ListKind && iterRange.AsList().Size() == 0 &&
return *l loopCond.Kind() == ast.LiteralKind && loopCond.AsLiteral() == types.False &&
} comp.AccuVar() != parser.AccumulatorName
// return an estimate of 1 for return types of set
// lengths, since strings/bytes/more complex objects could be of
// variable length
if isScalar(t.Type()) {
// TODO: since the logic for size estimation is split between
// ComputedSize and isScalar, changing one will likely require changing
// the other, so they should be merged in the future if possible
return SizeEstimate{Min: 1, Max: 1}
}
return SizeEstimate{Min: 0, Max: math.MaxUint64}
} }
func (c *coster) functionCost(function, overloadID string, target *AstNode, args []AstNode, argCosts []CostEstimate) CallEstimate { func (c *coster) costBind(e ast.Expr) CostEstimate {
comp := e.AsComprehension()
var sum CostEstimate
// Binds are lazily initialized, so we retain the cost of an empty iteration range.
sum = sum.Add(c.cost(comp.IterRange()))
sum = sum.Add(c.cost(comp.AccuInit()))
c.pushLocalVar(comp.AccuVar(), comp.AccuInit())
sum = sum.Add(c.cost(comp.Result()))
c.popLocalVar(comp.AccuVar())
// Associate the bind output size with the result size.
c.copySizeEstimates(e, comp.Result())
return sum
}
func (c *coster) functionCost(e ast.Expr, function, overloadID string, target *AstNode, args []AstNode, argCosts []CostEstimate) CallEstimate {
argCostSum := func() CostEstimate { argCostSum := func() CostEstimate {
var sum CostEstimate var sum CostEstimate
for _, a := range argCosts { for _, a := range argCosts {
@ -559,35 +747,42 @@ func (c *coster) functionCost(function, overloadID string, target *AstNode, args
case overloads.ExtFormatString: case overloads.ExtFormatString:
if target != nil { if target != nil {
// ResultSize not calculated because we can't bound the max size. // ResultSize not calculated because we can't bound the max size.
return CallEstimate{CostEstimate: c.sizeEstimate(*target).MultiplyByCostFactor(common.StringTraversalCostFactor).Add(argCostSum())} return CallEstimate{
CostEstimate: c.sizeOrUnknown(*target).MultiplyByCostFactor(common.StringTraversalCostFactor).Add(argCostSum())}
} }
case overloads.StringToBytes: case overloads.StringToBytes:
if len(args) == 1 { if len(args) == 1 {
sz := c.sizeEstimate(args[0]) sz := c.sizeOrUnknown(args[0])
// ResultSize max is when each char converts to 4 bytes. // ResultSize max is when each char converts to 4 bytes.
return CallEstimate{CostEstimate: sz.MultiplyByCostFactor(common.StringTraversalCostFactor).Add(argCostSum()), ResultSize: &SizeEstimate{Min: sz.Min, Max: sz.Max * 4}} return CallEstimate{
CostEstimate: sz.MultiplyByCostFactor(common.StringTraversalCostFactor).Add(argCostSum()),
ResultSize: &SizeEstimate{Min: sz.Min, Max: sz.Max * 4}}
} }
case overloads.BytesToString: case overloads.BytesToString:
if len(args) == 1 { if len(args) == 1 {
sz := c.sizeEstimate(args[0]) sz := c.sizeOrUnknown(args[0])
// ResultSize min is when 4 bytes convert to 1 char. // ResultSize min is when 4 bytes convert to 1 char.
return CallEstimate{CostEstimate: sz.MultiplyByCostFactor(common.StringTraversalCostFactor).Add(argCostSum()), ResultSize: &SizeEstimate{Min: sz.Min / 4, Max: sz.Max}} return CallEstimate{
CostEstimate: sz.MultiplyByCostFactor(common.StringTraversalCostFactor).Add(argCostSum()),
ResultSize: &SizeEstimate{Min: sz.Min / 4, Max: sz.Max}}
} }
case overloads.ExtQuoteString: case overloads.ExtQuoteString:
if len(args) == 1 { if len(args) == 1 {
sz := c.sizeEstimate(args[0]) sz := c.sizeOrUnknown(args[0])
// ResultSize max is when each char is escaped. 2 quote chars always added. // ResultSize max is when each char is escaped. 2 quote chars always added.
return CallEstimate{CostEstimate: sz.MultiplyByCostFactor(common.StringTraversalCostFactor).Add(argCostSum()), ResultSize: &SizeEstimate{Min: sz.Min + 2, Max: sz.Max*2 + 2}} return CallEstimate{
CostEstimate: sz.MultiplyByCostFactor(common.StringTraversalCostFactor).Add(argCostSum()),
ResultSize: &SizeEstimate{Min: sz.Min + 2, Max: sz.Max*2 + 2}}
} }
case overloads.StartsWithString, overloads.EndsWithString: case overloads.StartsWithString, overloads.EndsWithString:
if len(args) == 1 { if len(args) == 1 {
return CallEstimate{CostEstimate: c.sizeEstimate(args[0]).MultiplyByCostFactor(common.StringTraversalCostFactor).Add(argCostSum())} return CallEstimate{CostEstimate: c.sizeOrUnknown(args[0]).MultiplyByCostFactor(common.StringTraversalCostFactor).Add(argCostSum())}
} }
case overloads.InList: case overloads.InList:
// If a list is composed entirely of constant values this is O(1), but we don't account for that here. // If a list is composed entirely of constant values this is O(1), but we don't account for that here.
// We just assume all list containment checks are O(n). // We just assume all list containment checks are O(n).
if len(args) == 2 { if len(args) == 2 {
return CallEstimate{CostEstimate: c.sizeEstimate(args[1]).MultiplyByCostFactor(1).Add(argCostSum())} return CallEstimate{CostEstimate: c.sizeOrUnknown(args[1]).MultiplyByCostFactor(1).Add(argCostSum())}
} }
// O(nm) functions // O(nm) functions
case overloads.MatchesString: case overloads.MatchesString:
@ -595,19 +790,19 @@ func (c *coster) functionCost(function, overloadID string, target *AstNode, args
if target != nil && len(args) == 1 { if target != nil && len(args) == 1 {
// Add one to string length for purposes of cost calculation to prevent product of string and regex to be 0 // Add one to string length for purposes of cost calculation to prevent product of string and regex to be 0
// in case where string is empty but regex is still expensive. // in case where string is empty but regex is still expensive.
strCost := c.sizeEstimate(*target).Add(SizeEstimate{Min: 1, Max: 1}).MultiplyByCostFactor(common.StringTraversalCostFactor) strCost := c.sizeOrUnknown(*target).Add(SizeEstimate{Min: 1, Max: 1}).MultiplyByCostFactor(common.StringTraversalCostFactor)
// We don't know how many expressions are in the regex, just the string length (a huge // We don't know how many expressions are in the regex, just the string length (a huge
// improvement here would be to somehow get a count the number of expressions in the regex or // improvement here would be to somehow get a count the number of expressions in the regex or
// how many states are in the regex state machine and use that to measure regex cost). // how many states are in the regex state machine and use that to measure regex cost).
// For now, we're making a guess that each expression in a regex is typically at least 4 chars // For now, we're making a guess that each expression in a regex is typically at least 4 chars
// in length. // in length.
regexCost := c.sizeEstimate(args[0]).MultiplyByCostFactor(common.RegexStringLengthCostFactor) regexCost := c.sizeOrUnknown(args[0]).MultiplyByCostFactor(common.RegexStringLengthCostFactor)
return CallEstimate{CostEstimate: strCost.Multiply(regexCost).Add(argCostSum())} return CallEstimate{CostEstimate: strCost.Multiply(regexCost).Add(argCostSum())}
} }
case overloads.ContainsString: case overloads.ContainsString:
if target != nil && len(args) == 1 { if target != nil && len(args) == 1 {
strCost := c.sizeEstimate(*target).MultiplyByCostFactor(common.StringTraversalCostFactor) strCost := c.sizeOrUnknown(*target).MultiplyByCostFactor(common.StringTraversalCostFactor)
substrCost := c.sizeEstimate(args[0]).MultiplyByCostFactor(common.StringTraversalCostFactor) substrCost := c.sizeOrUnknown(args[0]).MultiplyByCostFactor(common.StringTraversalCostFactor)
return CallEstimate{CostEstimate: strCost.Multiply(substrCost).Add(argCostSum())} return CallEstimate{CostEstimate: strCost.Multiply(substrCost).Add(argCostSum())}
} }
case overloads.LogicalOr, overloads.LogicalAnd: case overloads.LogicalOr, overloads.LogicalAnd:
@ -617,7 +812,9 @@ func (c *coster) functionCost(function, overloadID string, target *AstNode, args
argCost := CostEstimate{Min: lhs.Min, Max: lhs.Add(rhs).Max} argCost := CostEstimate{Min: lhs.Min, Max: lhs.Add(rhs).Max}
return CallEstimate{CostEstimate: argCost} return CallEstimate{CostEstimate: argCost}
case overloads.Conditional: case overloads.Conditional:
size := c.sizeEstimate(args[1]).Union(c.sizeEstimate(args[2])) size := c.sizeOrUnknown(args[1]).Union(c.sizeOrUnknown(args[2]))
resultEntrySize := c.computeEntrySize(args[1].Expr()).union(c.computeEntrySize(args[2].Expr()))
c.setEntrySize(e, resultEntrySize)
conditionalCost := argCosts[0] conditionalCost := argCosts[0]
ifTrueCost := argCosts[1] ifTrueCost := argCosts[1]
ifFalseCost := argCosts[2] ifFalseCost := argCosts[2]
@ -625,13 +822,19 @@ func (c *coster) functionCost(function, overloadID string, target *AstNode, args
return CallEstimate{CostEstimate: argCost, ResultSize: &size} return CallEstimate{CostEstimate: argCost, ResultSize: &size}
case overloads.AddString, overloads.AddBytes, overloads.AddList: case overloads.AddString, overloads.AddBytes, overloads.AddList:
if len(args) == 2 { if len(args) == 2 {
lhsSize := c.sizeEstimate(args[0]) lhsSize := c.sizeOrUnknown(args[0])
rhsSize := c.sizeEstimate(args[1]) rhsSize := c.sizeOrUnknown(args[1])
resultSize := lhsSize.Add(rhsSize) resultSize := lhsSize.Add(rhsSize)
rhsEntrySize := c.computeEntrySize(args[0].Expr())
lhsEntrySize := c.computeEntrySize(args[1].Expr())
resultEntrySize := rhsEntrySize.union(lhsEntrySize)
if resultEntrySize != nil {
c.setEntrySize(e, resultEntrySize)
}
switch overloadID { switch overloadID {
case overloads.AddList: case overloads.AddList:
// list concatenation is O(1), but we handle it here to track size // list concatenation is O(1), but we handle it here to track size
return CallEstimate{CostEstimate: CostEstimate{Min: 1, Max: 1}.Add(argCostSum()), ResultSize: &resultSize} return CallEstimate{CostEstimate: FixedCostEstimate(1).Add(argCostSum()), ResultSize: &resultSize}
default: default:
return CallEstimate{CostEstimate: resultSize.MultiplyByCostFactor(common.StringTraversalCostFactor).Add(argCostSum()), ResultSize: &resultSize} return CallEstimate{CostEstimate: resultSize.MultiplyByCostFactor(common.StringTraversalCostFactor).Add(argCostSum()), ResultSize: &resultSize}
} }
@ -639,8 +842,8 @@ func (c *coster) functionCost(function, overloadID string, target *AstNode, args
case overloads.LessString, overloads.GreaterString, overloads.LessEqualsString, overloads.GreaterEqualsString, case overloads.LessString, overloads.GreaterString, overloads.LessEqualsString, overloads.GreaterEqualsString,
overloads.LessBytes, overloads.GreaterBytes, overloads.LessEqualsBytes, overloads.GreaterEqualsBytes, overloads.LessBytes, overloads.GreaterBytes, overloads.LessEqualsBytes, overloads.GreaterEqualsBytes,
overloads.Equals, overloads.NotEquals: overloads.Equals, overloads.NotEquals:
lhsCost := c.sizeEstimate(args[0]) lhsCost := c.sizeOrUnknown(args[0])
rhsCost := c.sizeEstimate(args[1]) rhsCost := c.sizeOrUnknown(args[1])
min := uint64(0) min := uint64(0)
smallestMax := lhsCost.Max smallestMax := lhsCost.Max
if rhsCost.Max < smallestMax { if rhsCost.Max < smallestMax {
@ -650,14 +853,16 @@ func (c *coster) functionCost(function, overloadID string, target *AstNode, args
min = 1 min = 1
} }
// equality of 2 scalar values results in a cost of 1 // equality of 2 scalar values results in a cost of 1
return CallEstimate{CostEstimate: CostEstimate{Min: min, Max: smallestMax}.MultiplyByCostFactor(common.StringTraversalCostFactor).Add(argCostSum())} return CallEstimate{
CostEstimate: CostEstimate{Min: min, Max: smallestMax}.MultiplyByCostFactor(common.StringTraversalCostFactor).Add(argCostSum()),
}
} }
// O(1) functions // O(1) functions
// See CostTracker.costCall for more details about O(1) cost calculations // See CostTracker.costCall for more details about O(1) cost calculations
// Benchmarks suggest that most of the other operations take +/- 50% of a base cost unit // Benchmarks suggest that most of the other operations take +/- 50% of a base cost unit
// which on an Intel xeon 2.20GHz CPU is 50ns. // which on an Intel xeon 2.20GHz CPU is 50ns.
return CallEstimate{CostEstimate: CostEstimate{Min: 1, Max: 1}.Add(argCostSum())} return CallEstimate{CostEstimate: FixedCostEstimate(1).Add(argCostSum())}
} }
func (c *coster) getType(e ast.Expr) *types.Type { func (c *coster) getType(e ast.Expr) *types.Type {
@ -665,28 +870,145 @@ func (c *coster) getType(e ast.Expr) *types.Type {
} }
func (c *coster) getPath(e ast.Expr) []string { func (c *coster) getPath(e ast.Expr) []string {
return c.exprPath[e.ID()] if e.Kind() == ast.IdentKind {
if v, found := c.peekLocalVar(e.AsIdent()); found {
return v.path[:]
}
}
return c.exprPaths[e.ID()][:]
} }
func (c *coster) addPath(e ast.Expr, path []string) { func (c *coster) addPath(e ast.Expr, path []string) {
c.exprPath[e.ID()] = path c.exprPaths[e.ID()] = path
}
func isAccumulatorVar(name string) bool {
return name == parser.AccumulatorName || name == parser.HiddenAccumulatorName
} }
func (c *coster) newAstNode(e ast.Expr) *astNode { func (c *coster) newAstNode(e ast.Expr) *astNode {
path := c.getPath(e) path := c.getPath(e)
if len(path) > 0 && path[0] == parser.AccumulatorName { if len(path) > 0 && isAccumulatorVar(path[0]) {
// only provide paths to root vars; omit accumulator vars // only provide paths to root vars; omit accumulator vars
path = nil path = nil
} }
var derivedSize *SizeEstimate
if size, ok := c.computedSizes[e.ID()]; ok {
derivedSize = &size
}
return &astNode{ return &astNode{
path: path, path: path,
t: c.getType(e), t: c.getType(e),
expr: e, expr: e,
derivedSize: derivedSize} derivedSize: c.computeSize(e)}
}
func (c *coster) setSize(e ast.Expr, size *SizeEstimate) {
if size == nil {
return
}
// Store the computed size with the expression
c.computedSizes[e.ID()] = *size
}
func (c *coster) sizeOrUnknown(node any) SizeEstimate {
switch v := node.(type) {
case ast.Expr:
if sz := c.computeSize(v); sz != nil {
return *sz
}
case AstNode:
if sz := v.ComputedSize(); sz != nil {
return *sz
}
}
return UnknownSizeEstimate()
}
func (c *coster) copySizeEstimates(dst, src ast.Expr) {
c.setSize(dst, c.computeSize(src))
c.setEntrySize(dst, c.computeEntrySize(src))
}
func (c *coster) computeSize(e ast.Expr) *SizeEstimate {
if size, ok := c.computedSizes[e.ID()]; ok {
return &size
}
if size := computeExprSize(e); size != nil {
return size
}
// Ensure size estimates are computed first as users may choose to override the costs that
// CEL would otherwise ascribe to the type.
node := astNode{expr: e, path: c.getPath(e), t: c.getType(e)}
if size := c.estimator.EstimateSize(node); size != nil {
// storing the computed size should reduce calls to EstimateSize()
c.computedSizes[e.ID()] = *size
return size
}
if size := computeTypeSize(c.getType(e)); size != nil {
return size
}
if e.Kind() == ast.IdentKind {
varName := e.AsIdent()
if v, ok := c.peekLocalVar(varName); ok && v.size != nil {
return v.size
}
}
return nil
}
func (c *coster) setEntrySize(e ast.Expr, size *entrySizeEstimate) {
if size == nil {
return
}
c.computedEntrySizes[e.ID()] = *size
}
func (c *coster) computeEntrySize(e ast.Expr) *entrySizeEstimate {
if sz, found := c.computedEntrySizes[e.ID()]; found {
return &sz
}
if e.Kind() == ast.IdentKind {
varName := e.AsIdent()
if v, ok := c.peekLocalVar(varName); ok && v.entrySize != nil {
return v.entrySize
}
}
return nil
}
func computeExprSize(expr ast.Expr) *SizeEstimate {
var v uint64
switch expr.Kind() {
case ast.LiteralKind:
switch ck := expr.AsLiteral().(type) {
case types.String:
// converting to runes here is an O(n) operation, but
// this is consistent with how size is computed at runtime,
// and how the language definition defines string size
v = uint64(len([]rune(ck)))
case types.Bytes:
v = uint64(len(ck))
case types.Bool, types.Double, types.Duration,
types.Int, types.Timestamp, types.Uint,
types.Null:
v = uint64(1)
default:
return nil
}
case ast.ListKind:
v = uint64(expr.AsList().Size())
case ast.MapKind:
v = uint64(expr.AsMap().Size())
default:
return nil
}
cost := FixedSizeEstimate(v)
return &cost
}
func computeTypeSize(t *types.Type) *SizeEstimate {
if isScalar(t) {
cost := FixedSizeEstimate(1)
return &cost
}
return nil
} }
// isScalar returns true if the given type is known to be of a constant size at // isScalar returns true if the given type is known to be of a constant size at
@ -696,10 +1018,24 @@ func isScalar(t *types.Type) bool {
switch t.Kind() { switch t.Kind() {
case types.BoolKind, types.DoubleKind, types.DurationKind, types.IntKind, types.TimestampKind, types.UintKind: case types.BoolKind, types.DoubleKind, types.DurationKind, types.IntKind, types.TimestampKind, types.UintKind:
return true return true
case types.OpaqueKind:
if t.TypeName() == "optional_type" {
return isScalar(t.Parameters()[0])
}
} }
return false return false
} }
var ( var (
doubleTwoTo64 = math.Ldexp(1.0, 64) doubleTwoTo64 = math.Ldexp(1.0, 64)
unknownSizeEstimate = SizeEstimate{Min: 0, Max: math.MaxUint64}
unknownCostEstimate = unknownSizeEstimate.MultiplyByCostFactor(1)
selectAndIdentCost = FixedCostEstimate(common.SelectAndIdentCost)
constCost = FixedCostEstimate(common.ConstCost)
createListBaseCost = FixedCostEstimate(common.ListCreateBaseCost)
createMapBaseCost = FixedCostEstimate(common.MapCreateBaseCost)
createMessageBaseCost = FixedCostEstimate(common.StructCreateBaseCost)
) )

View File

@ -43,12 +43,15 @@ type ExprFactory interface {
// comprehension. // comprehension.
NewAccuIdent(id int64) Expr NewAccuIdent(id int64) Expr
// AccuIdentName reports the name of the accumulator variable to be used within a comprehension.
AccuIdentName() string
// NewLiteral creates an Expr value representing a literal value, such as a string or integer. // NewLiteral creates an Expr value representing a literal value, such as a string or integer.
NewLiteral(id int64, value ref.Val) Expr NewLiteral(id int64, value ref.Val) Expr
// NewList creates an Expr value representing a list literal expression with optional indices. // NewList creates an Expr value representing a list literal expression with optional indices.
// //
// Optional indicies will typically be empty unless the CEL optional types are enabled. // Optional indices will typically be empty unless the CEL optional types are enabled.
NewList(id int64, elems []Expr, optIndices []int32) Expr NewList(id int64, elems []Expr, optIndices []int32) Expr
// NewMap creates an Expr value representing a map literal expression // NewMap creates an Expr value representing a map literal expression
@ -78,11 +81,23 @@ type ExprFactory interface {
isExprFactory() isExprFactory()
} }
type baseExprFactory struct{} type baseExprFactory struct {
accumulatorName string
}
// NewExprFactory creates an ExprFactory instance. // NewExprFactory creates an ExprFactory instance.
func NewExprFactory() ExprFactory { func NewExprFactory() ExprFactory {
return &baseExprFactory{} return &baseExprFactory{
"@result",
}
}
// NewExprFactoryWithAccumulator creates an ExprFactory instance with a custom
// accumulator identifier name.
func NewExprFactoryWithAccumulator(id string) ExprFactory {
return &baseExprFactory{
id,
}
} }
func (fac *baseExprFactory) NewCall(id int64, function string, args ...Expr) Expr { func (fac *baseExprFactory) NewCall(id int64, function string, args ...Expr) Expr {
@ -138,7 +153,11 @@ func (fac *baseExprFactory) NewIdent(id int64, name string) Expr {
} }
func (fac *baseExprFactory) NewAccuIdent(id int64) Expr { func (fac *baseExprFactory) NewAccuIdent(id int64) Expr {
return fac.NewIdent(id, "__result__") return fac.NewIdent(id, fac.AccuIdentName())
}
func (fac *baseExprFactory) AccuIdentName() string {
return fac.accumulatorName
} }
func (fac *baseExprFactory) NewLiteral(id int64, value ref.Val) Expr { func (fac *baseExprFactory) NewLiteral(id int64, value ref.Val) Expr {

View File

@ -257,7 +257,7 @@ func formatLiteral(c ref.Val) string {
case types.Bool: case types.Bool:
return fmt.Sprintf("%t", v) return fmt.Sprintf("%t", v)
case types.Bytes: case types.Bytes:
return fmt.Sprintf("b\"%s\"", string(v)) return fmt.Sprintf("b%s", strconv.Quote(string(v)))
case types.Double: case types.Double:
return fmt.Sprintf("%v", float64(v)) return fmt.Sprintf("%v", float64(v))
case types.Int: case types.Int:

View File

@ -782,6 +782,11 @@ func TypeVariable(t *types.Type) *VariableDecl {
return NewVariable(t.TypeName(), types.NewTypeTypeWithParam(t)) return NewVariable(t.TypeName(), types.NewTypeTypeWithParam(t))
} }
// VariableDeclToExprDecl converts a go-native variable declaration into a protobuf-type variable declaration.
func VariableDeclToExprDecl(v *VariableDecl) (*exprpb.Decl, error) {
return variableDeclToExprDecl(v)
}
// variableDeclToExprDecl converts a go-native variable declaration into a protobuf-type variable declaration. // variableDeclToExprDecl converts a go-native variable declaration into a protobuf-type variable declaration.
func variableDeclToExprDecl(v *VariableDecl) (*exprpb.Decl, error) { func variableDeclToExprDecl(v *VariableDecl) (*exprpb.Decl, error) {
varType, err := types.TypeToExprType(v.Type()) varType, err := types.TypeToExprType(v.Type())
@ -791,6 +796,11 @@ func variableDeclToExprDecl(v *VariableDecl) (*exprpb.Decl, error) {
return chkdecls.NewVar(v.Name(), varType), nil return chkdecls.NewVar(v.Name(), varType), nil
} }
// FunctionDeclToExprDecl converts a go-native function declaration into a protobuf-typed function declaration.
func FunctionDeclToExprDecl(f *FunctionDecl) (*exprpb.Decl, error) {
return functionDeclToExprDecl(f)
}
// functionDeclToExprDecl converts a go-native function declaration into a protobuf-typed function declaration. // functionDeclToExprDecl converts a go-native function declaration into a protobuf-typed function declaration.
func functionDeclToExprDecl(f *FunctionDecl) (*exprpb.Decl, error) { func functionDeclToExprDecl(f *FunctionDecl) (*exprpb.Decl, error) {
overloads := make([]*exprpb.Decl_FunctionDecl_Overload, len(f.overloads)) overloads := make([]*exprpb.Decl_FunctionDecl_Overload, len(f.overloads))

View File

@ -30,9 +30,13 @@ type Errors struct {
// NewErrors creates a new instance of the Errors type. // NewErrors creates a new instance of the Errors type.
func NewErrors(source Source) *Errors { func NewErrors(source Source) *Errors {
src := source
if src == nil {
src = NewTextSource("")
}
return &Errors{ return &Errors{
errors: []*Error{}, errors: []*Error{},
source: source, source: src,
maxErrorsToReport: 100, maxErrorsToReport: 100,
} }
} }
@ -42,6 +46,11 @@ func (e *Errors) ReportError(l Location, format string, args ...any) {
e.ReportErrorAtID(0, l, format, args...) e.ReportErrorAtID(0, l, format, args...)
} }
// ReportErrorString records an error at a source location.
func (e *Errors) ReportErrorString(l Location, message string) {
e.ReportErrorAtID(0, l, "%s", message)
}
// ReportErrorAtID records an error at a source location and expression id. // ReportErrorAtID records an error at a source location and expression id.
func (e *Errors) ReportErrorAtID(id int64, l Location, format string, args ...any) { func (e *Errors) ReportErrorAtID(id int64, l Location, format string, args ...any) {
e.numErrors++ e.numErrors++

View File

@ -62,6 +62,12 @@ func NewErr(format string, args ...any) ref.Val {
return &Err{error: fmt.Errorf(format, args...)} return &Err{error: fmt.Errorf(format, args...)}
} }
// NewErrFromString creates a new Err with the provided message.
// TODO: Audit the use of this function and standardize the error messages and codes.
func NewErrFromString(message string) ref.Val {
return &Err{error: errors.New(message)}
}
// NewErrWithNodeID creates a new Err described by the format string and args. // NewErrWithNodeID creates a new Err described by the format string and args.
// TODO: Audit the use of this function and standardize the error messages and codes. // TODO: Audit the use of this function and standardize the error messages and codes.
func NewErrWithNodeID(id int64, format string, args ...any) ref.Val { func NewErrWithNodeID(id int64, format string, args ...any) ref.Val {

View File

@ -243,7 +243,7 @@ func (l *baseList) Equal(other ref.Val) ref.Val {
func (l *baseList) Get(index ref.Val) ref.Val { func (l *baseList) Get(index ref.Val) ref.Val {
ind, err := IndexOrError(index) ind, err := IndexOrError(index)
if err != nil { if err != nil {
return ValOrErr(index, err.Error()) return ValOrErr(index, "%v", err)
} }
if ind < 0 || ind >= l.size { if ind < 0 || ind >= l.size {
return NewErr("index '%d' out of range in list size '%d'", ind, l.Size()) return NewErr("index '%d' out of range in list size '%d'", ind, l.Size())
@ -427,7 +427,7 @@ func (l *concatList) Equal(other ref.Val) ref.Val {
func (l *concatList) Get(index ref.Val) ref.Val { func (l *concatList) Get(index ref.Val) ref.Val {
ind, err := IndexOrError(index) ind, err := IndexOrError(index)
if err != nil { if err != nil {
return ValOrErr(index, err.Error()) return ValOrErr(index, "%v", err)
} }
i := Int(ind) i := Int(ind)
if i < l.prevList.Size().(Int) { if i < l.prevList.Size().(Int) {

View File

@ -151,7 +151,7 @@ func (o *protoObj) Get(index ref.Val) ref.Val {
} }
fv, err := fd.GetFrom(o.value) fv, err := fd.GetFrom(o.value)
if err != nil { if err != nil {
return NewErr(err.Error()) return NewErrFromString(err.Error())
} }
return o.NativeToValue(fv) return o.NativeToValue(fv)
} }

View File

@ -768,6 +768,19 @@ func ProtoAsType(t *celpb.Type) (*Type, error) {
} }
} }
// TypeToProto converts from a CEL-native type representation to canonical CEL celpb.Type protobuf type.
func TypeToProto(t *Type) (*celpb.Type, error) {
exprType, err := TypeToExprType(t)
if err != nil {
return nil, err
}
var pbtype celpb.Type
if err = convertProto(exprType, &pbtype); err != nil {
return nil, err
}
return &pbtype, nil
}
func maybeWrapper(t *Type, pbType *exprpb.Type) *exprpb.Type { func maybeWrapper(t *Type, pbType *exprpb.Type) *exprpb.Type {
if t.IsAssignableType(NullType) { if t.IsAssignableType(NullType) {
return chkdecls.NewWrapperType(pbType) return chkdecls.NewWrapperType(pbType)

View File

@ -8,6 +8,7 @@ go_library(
name = "go_default_library", name = "go_default_library",
srcs = [ srcs = [
"bindings.go", "bindings.go",
"comprehensions.go",
"encoders.go", "encoders.go",
"formatting.go", "formatting.go",
"guards.go", "guards.go",
@ -45,6 +46,8 @@ go_test(
name = "go_default_test", name = "go_default_test",
size = "small", size = "small",
srcs = [ srcs = [
"bindings_test.go",
"comprehensions_test.go",
"encoders_test.go", "encoders_test.go",
"lists_test.go", "lists_test.go",
"math_test.go", "math_test.go",

View File

@ -11,7 +11,7 @@ in expressions.
### Cel.Bind ### Cel.Bind
Binds a simple identifier to an initialization expression which may be used Binds a simple identifier to an initialization expression which may be used
in a subsequenct result expression. Bindings may also be nested within each in a subsequent result expression. Bindings may also be nested within each
other. other.
cel.bind(<varName>, <initExpr>, <resultExpr>) cel.bind(<varName>, <initExpr>, <resultExpr>)
@ -29,7 +29,7 @@ Local bindings are not guaranteed to be evaluated before use.
## Encoders ## Encoders
Encoding utilies for marshalling data into standardized representations. Encoding utilities for marshalling data into standardized representations.
### Base64.Decode ### Base64.Decode
@ -500,6 +500,36 @@ Examples:
].sortBy(e, e.score).map(e, e.name) ].sortBy(e, e.score).map(e, e.name)
== ["bar", "foo", "baz"] == ["bar", "foo", "baz"]
### Last
**Introduced in the OptionalTypes library version 2**
Returns an optional with the last value from the list or `optional.None` if the
list is empty.
<list(T)>.last() -> <Optional(T)>
Examples:
[1, 2, 3].last().value() == 3
[].last().orValue('test') == 'test'
This is syntactic sugar for list[list.size()-1].
### First
**Introduced in the OptionalTypes library version 2**
Returns an optional with the first value from the list or `optional.None` if the
list is empty.
<list(T)>.first() -> <Optional(T)>
Examples:
[1, 2, 3].first().value() == 1
[].first().orValue('test') == 'test'
## Sets ## Sets
Sets provides set relationship tests. Sets provides set relationship tests.

View File

@ -16,6 +16,7 @@ package ext
import ( import (
"fmt" "fmt"
"math"
"github.com/google/cel-go/cel" "github.com/google/cel-go/cel"
"github.com/google/cel-go/common/ast" "github.com/google/cel-go/common/ast"
@ -159,19 +160,36 @@ const (
// //
// {'greeting': 'aloha', 'farewell': 'aloha'} // {'greeting': 'aloha', 'farewell': 'aloha'}
// .transformMapEntry(keyVar, valueVar, {valueVar: keyVar}) // error, duplicate key // .transformMapEntry(keyVar, valueVar, {valueVar: keyVar}) // error, duplicate key
func TwoVarComprehensions() cel.EnvOption { func TwoVarComprehensions(options ...TwoVarComprehensionsOption) cel.EnvOption {
return cel.Lib(compreV2Lib{}) l := &compreV2Lib{version: math.MaxUint32}
for _, o := range options {
l = o(l)
}
return cel.Lib(l)
} }
type compreV2Lib struct{} // TwoVarComprehensionsOption declares a functional operator for configuring two-variable comprehensions.
type TwoVarComprehensionsOption func(*compreV2Lib) *compreV2Lib
// TwoVarComprehensionsVersion sets the library version for two-variable comprehensions.
func TwoVarComprehensionsVersion(version uint32) TwoVarComprehensionsOption {
return func(lib *compreV2Lib) *compreV2Lib {
lib.version = version
return lib
}
}
type compreV2Lib struct {
version uint32
}
// LibraryName implements that SingletonLibrary interface method. // LibraryName implements that SingletonLibrary interface method.
func (compreV2Lib) LibraryName() string { func (*compreV2Lib) LibraryName() string {
return "cel.lib.ext.comprev2" return "cel.lib.ext.comprev2"
} }
// CompileOptions implements the cel.Library interface method. // CompileOptions implements the cel.Library interface method.
func (compreV2Lib) CompileOptions() []cel.EnvOption { func (*compreV2Lib) CompileOptions() []cel.EnvOption {
kType := cel.TypeParamType("K") kType := cel.TypeParamType("K")
vType := cel.TypeParamType("V") vType := cel.TypeParamType("V")
mapKVType := cel.MapType(kType, vType) mapKVType := cel.MapType(kType, vType)
@ -217,7 +235,7 @@ func (compreV2Lib) CompileOptions() []cel.EnvOption {
} }
// ProgramOptions implements the cel.Library interface method // ProgramOptions implements the cel.Library interface method
func (compreV2Lib) ProgramOptions() []cel.ProgramOption { func (*compreV2Lib) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{} return []cel.ProgramOption{}
} }
@ -231,7 +249,7 @@ func quantifierAll(mef cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (
target, target,
iterVar1, iterVar1,
iterVar2, iterVar2,
parser.AccumulatorName, mef.AccuIdentName(),
/*accuInit=*/ mef.NewLiteral(types.True), /*accuInit=*/ mef.NewLiteral(types.True),
/*condition=*/ mef.NewCall(operators.NotStrictlyFalse, mef.NewAccuIdent()), /*condition=*/ mef.NewCall(operators.NotStrictlyFalse, mef.NewAccuIdent()),
/*step=*/ mef.NewCall(operators.LogicalAnd, mef.NewAccuIdent(), args[2]), /*step=*/ mef.NewCall(operators.LogicalAnd, mef.NewAccuIdent(), args[2]),
@ -249,7 +267,7 @@ func quantifierExists(mef cel.MacroExprFactory, target ast.Expr, args []ast.Expr
target, target,
iterVar1, iterVar1,
iterVar2, iterVar2,
parser.AccumulatorName, mef.AccuIdentName(),
/*accuInit=*/ mef.NewLiteral(types.False), /*accuInit=*/ mef.NewLiteral(types.False),
/*condition=*/ mef.NewCall(operators.NotStrictlyFalse, mef.NewCall(operators.LogicalNot, mef.NewAccuIdent())), /*condition=*/ mef.NewCall(operators.NotStrictlyFalse, mef.NewCall(operators.LogicalNot, mef.NewAccuIdent())),
/*step=*/ mef.NewCall(operators.LogicalOr, mef.NewAccuIdent(), args[2]), /*step=*/ mef.NewCall(operators.LogicalOr, mef.NewAccuIdent(), args[2]),
@ -267,7 +285,7 @@ func quantifierExistsOne(mef cel.MacroExprFactory, target ast.Expr, args []ast.E
target, target,
iterVar1, iterVar1,
iterVar2, iterVar2,
parser.AccumulatorName, mef.AccuIdentName(),
/*accuInit=*/ mef.NewLiteral(types.Int(0)), /*accuInit=*/ mef.NewLiteral(types.Int(0)),
/*condition=*/ mef.NewLiteral(types.True), /*condition=*/ mef.NewLiteral(types.True),
/*step=*/ mef.NewCall(operators.Conditional, args[2], /*step=*/ mef.NewCall(operators.Conditional, args[2],
@ -293,10 +311,10 @@ func transformList(mef cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (
transform = args[2] transform = args[2]
} }
// __result__ = __result__ + [transform] // accumulator = accumulator + [transform]
step := mef.NewCall(operators.Add, mef.NewAccuIdent(), mef.NewList(transform)) step := mef.NewCall(operators.Add, mef.NewAccuIdent(), mef.NewList(transform))
if filter != nil { if filter != nil {
// __result__ = (filter) ? __result__ + [transform] : __result__ // accumulator = (filter) ? accumulator + [transform] : accumulator
step = mef.NewCall(operators.Conditional, filter, step, mef.NewAccuIdent()) step = mef.NewCall(operators.Conditional, filter, step, mef.NewAccuIdent())
} }
@ -304,7 +322,7 @@ func transformList(mef cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (
target, target,
iterVar1, iterVar1,
iterVar2, iterVar2,
parser.AccumulatorName, mef.AccuIdentName(),
/*accuInit=*/ mef.NewList(), /*accuInit=*/ mef.NewList(),
/*condition=*/ mef.NewLiteral(types.True), /*condition=*/ mef.NewLiteral(types.True),
step, step,
@ -328,17 +346,17 @@ func transformMap(mef cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (a
transform = args[2] transform = args[2]
} }
// __result__ = cel.@mapInsert(__result__, iterVar1, transform) // accumulator = cel.@mapInsert(accumulator, iterVar1, transform)
step := mef.NewCall(mapInsert, mef.NewAccuIdent(), mef.NewIdent(iterVar1), transform) step := mef.NewCall(mapInsert, mef.NewAccuIdent(), mef.NewIdent(iterVar1), transform)
if filter != nil { if filter != nil {
// __result__ = (filter) ? cel.@mapInsert(__result__, iterVar1, transform) : __result__ // accumulator = (filter) ? cel.@mapInsert(accumulator, iterVar1, transform) : accumulator
step = mef.NewCall(operators.Conditional, filter, step, mef.NewAccuIdent()) step = mef.NewCall(operators.Conditional, filter, step, mef.NewAccuIdent())
} }
return mef.NewComprehensionTwoVar( return mef.NewComprehensionTwoVar(
target, target,
iterVar1, iterVar1,
iterVar2, iterVar2,
parser.AccumulatorName, mef.AccuIdentName(),
/*accuInit=*/ mef.NewMap(), /*accuInit=*/ mef.NewMap(),
/*condition=*/ mef.NewLiteral(types.True), /*condition=*/ mef.NewLiteral(types.True),
step, step,
@ -362,17 +380,17 @@ func transformMapEntry(mef cel.MacroExprFactory, target ast.Expr, args []ast.Exp
transform = args[2] transform = args[2]
} }
// __result__ = cel.@mapInsert(__result__, transform) // accumulator = cel.@mapInsert(accumulator, transform)
step := mef.NewCall(mapInsert, mef.NewAccuIdent(), transform) step := mef.NewCall(mapInsert, mef.NewAccuIdent(), transform)
if filter != nil { if filter != nil {
// __result__ = (filter) ? cel.@mapInsert(__result__, transform) : __result__ // accumulator = (filter) ? cel.@mapInsert(accumulator, transform) : accumulator
step = mef.NewCall(operators.Conditional, filter, step, mef.NewAccuIdent()) step = mef.NewCall(operators.Conditional, filter, step, mef.NewAccuIdent())
} }
return mef.NewComprehensionTwoVar( return mef.NewComprehensionTwoVar(
target, target,
iterVar1, iterVar1,
iterVar2, iterVar2,
parser.AccumulatorName, mef.AccuIdentName(),
/*accuInit=*/ mef.NewMap(), /*accuInit=*/ mef.NewMap(),
/*condition=*/ mef.NewLiteral(types.True), /*condition=*/ mef.NewLiteral(types.True),
step, step,
@ -392,10 +410,10 @@ func extractIterVars(mef cel.MacroExprFactory, arg0, arg1 ast.Expr) (string, str
if iterVar1 == iterVar2 { if iterVar1 == iterVar2 {
return "", "", mef.NewError(arg1.ID(), fmt.Sprintf("duplicate variable name: %s", iterVar1)) return "", "", mef.NewError(arg1.ID(), fmt.Sprintf("duplicate variable name: %s", iterVar1))
} }
if iterVar1 == parser.AccumulatorName { if iterVar1 == mef.AccuIdentName() || iterVar1 == parser.AccumulatorName {
return "", "", mef.NewError(arg0.ID(), "iteration variable overwrites accumulator variable") return "", "", mef.NewError(arg0.ID(), "iteration variable overwrites accumulator variable")
} }
if iterVar2 == parser.AccumulatorName { if iterVar2 == mef.AccuIdentName() || iterVar2 == parser.AccumulatorName {
return "", "", mef.NewError(arg1.ID(), "iteration variable overwrites accumulator variable") return "", "", mef.NewError(arg1.ID(), "iteration variable overwrites accumulator variable")
} }
return iterVar1, iterVar2, nil return iterVar1, iterVar2, nil

View File

@ -16,6 +16,7 @@ package ext
import ( import (
"encoding/base64" "encoding/base64"
"math"
"github.com/google/cel-go/cel" "github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types"
@ -47,17 +48,34 @@ import (
// Examples: // Examples:
// //
// base64.encode(b'hello') // return b'aGVsbG8=' // base64.encode(b'hello') // return b'aGVsbG8='
func Encoders() cel.EnvOption { func Encoders(options ...EncodersOption) cel.EnvOption {
return cel.Lib(encoderLib{}) l := &encoderLib{version: math.MaxUint32}
for _, o := range options {
l = o(l)
}
return cel.Lib(l)
} }
type encoderLib struct{} // EncodersOption declares a functional operator for configuring encoder extensions.
type EncodersOption func(*encoderLib) *encoderLib
func (encoderLib) LibraryName() string { // EncodersVersion sets the library version for encoder extensions.
func EncodersVersion(version uint32) EncodersOption {
return func(lib *encoderLib) *encoderLib {
lib.version = version
return lib
}
}
type encoderLib struct {
version uint32
}
func (*encoderLib) LibraryName() string {
return "cel.lib.ext.encoders" return "cel.lib.ext.encoders"
} }
func (encoderLib) CompileOptions() []cel.EnvOption { func (*encoderLib) CompileOptions() []cel.EnvOption {
return []cel.EnvOption{ return []cel.EnvOption{
cel.Function("base64.decode", cel.Function("base64.decode",
cel.Overload("base64_decode_string", []*cel.Type{cel.StringType}, cel.BytesType, cel.Overload("base64_decode_string", []*cel.Type{cel.StringType}, cel.BytesType,
@ -74,7 +92,7 @@ func (encoderLib) CompileOptions() []cel.EnvOption {
} }
} }
func (encoderLib) ProgramOptions() []cel.ProgramOption { func (*encoderLib) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{} return []cel.ProgramOption{}
} }

View File

@ -434,7 +434,7 @@ func (stringFormatValidator) Validate(env *cel.Env, _ cel.ValidatorConfig, a *as
// use a placeholder locale, since locale doesn't affect syntax // use a placeholder locale, since locale doesn't affect syntax
_, err := parseFormatString(formatStr, formatCheck, formatCheck, "en_US") _, err := parseFormatString(formatStr, formatCheck, formatCheck, "en_US")
if err != nil { if err != nil {
iss.ReportErrorAtID(getErrorExprID(e.ID(), err), err.Error()) iss.ReportErrorAtID(getErrorExprID(e.ID(), err), "%v", err)
continue continue
} }
seenArgs := formatCheck.argsRequested seenArgs := formatCheck.argsRequested

View File

@ -24,28 +24,28 @@ import (
func intOrError(i int64, err error) ref.Val { func intOrError(i int64, err error) ref.Val {
if err != nil { if err != nil {
return types.NewErr(err.Error()) return types.NewErrFromString(err.Error())
} }
return types.Int(i) return types.Int(i)
} }
func bytesOrError(bytes []byte, err error) ref.Val { func bytesOrError(bytes []byte, err error) ref.Val {
if err != nil { if err != nil {
return types.NewErr(err.Error()) return types.NewErrFromString(err.Error())
} }
return types.Bytes(bytes) return types.Bytes(bytes)
} }
func stringOrError(str string, err error) ref.Val { func stringOrError(str string, err error) ref.Val {
if err != nil { if err != nil {
return types.NewErr(err.Error()) return types.NewErrFromString(err.Error())
} }
return types.String(str) return types.String(str)
} }
func listStringOrError(strs []string, err error) ref.Val { func listStringOrError(strs []string, err error) ref.Val {
if err != nil { if err != nil {
return types.NewErr(err.Error()) return types.NewErrFromString(err.Error())
} }
return types.DefaultTypeAdapter.NativeToValue(strs) return types.DefaultTypeAdapter.NativeToValue(strs)
} }

View File

@ -145,13 +145,10 @@ var comparableTypes = []*cel.Type{
// == ["bar", "foo", "baz"] // == ["bar", "foo", "baz"]
func Lists(options ...ListsOption) cel.EnvOption { func Lists(options ...ListsOption) cel.EnvOption {
l := &listsLib{ l := &listsLib{version: math.MaxUint32}
version: math.MaxUint32,
}
for _, o := range options { for _, o := range options {
l = o(l) l = o(l)
} }
return cel.Lib(l) return cel.Lib(l)
} }
@ -211,9 +208,10 @@ func (lib listsLib) CompileOptions() []cel.EnvOption {
cel.MemberOverload("list_flatten", cel.MemberOverload("list_flatten",
[]*cel.Type{listListType}, listType, []*cel.Type{listListType}, listType,
cel.UnaryBinding(func(arg ref.Val) ref.Val { cel.UnaryBinding(func(arg ref.Val) ref.Val {
// double-check as type-guards disabled
list, ok := arg.(traits.Lister) list, ok := arg.(traits.Lister)
if !ok { if !ok {
return types.MaybeNoSuchOverloadErr(arg) return types.ValOrErr(arg, "no such overload: %v.flatten()", arg.Type())
} }
flatList, err := flatten(list, 1) flatList, err := flatten(list, 1)
if err != nil { if err != nil {
@ -226,13 +224,14 @@ func (lib listsLib) CompileOptions() []cel.EnvOption {
cel.MemberOverload("list_flatten_int", cel.MemberOverload("list_flatten_int",
[]*cel.Type{listDyn, types.IntType}, listDyn, []*cel.Type{listDyn, types.IntType}, listDyn,
cel.BinaryBinding(func(arg1, arg2 ref.Val) ref.Val { cel.BinaryBinding(func(arg1, arg2 ref.Val) ref.Val {
// double-check as type-guards disabled
list, ok := arg1.(traits.Lister) list, ok := arg1.(traits.Lister)
if !ok { if !ok {
return types.MaybeNoSuchOverloadErr(arg1) return types.ValOrErr(arg1, "no such overload: %v.flatten(%v)", arg1.Type(), arg2.Type())
} }
depth, ok := arg2.(types.Int) depth, ok := arg2.(types.Int)
if !ok { if !ok {
return types.MaybeNoSuchOverloadErr(arg2) return types.ValOrErr(arg1, "no such overload: %v.flatten(%v)", arg1.Type(), arg2.Type())
} }
flatList, err := flatten(list, int64(depth)) flatList, err := flatten(list, int64(depth))
if err != nil { if err != nil {
@ -260,10 +259,8 @@ func (lib listsLib) CompileOptions() []cel.EnvOption {
}), }),
cel.SingletonUnaryBinding( cel.SingletonUnaryBinding(
func(arg ref.Val) ref.Val { func(arg ref.Val) ref.Val {
list, ok := arg.(traits.Lister) // validated by type-guards
if !ok { list := arg.(traits.Lister)
return types.MaybeNoSuchOverloadErr(arg)
}
sorted, err := sortList(list) sorted, err := sortList(list)
if err != nil { if err != nil {
return types.WrapErr(err) return types.WrapErr(err)
@ -287,15 +284,10 @@ func (lib listsLib) CompileOptions() []cel.EnvOption {
) )
}), }),
cel.SingletonBinaryBinding( cel.SingletonBinaryBinding(
func(arg1 ref.Val, arg2 ref.Val) ref.Val { func(arg1, arg2 ref.Val) ref.Val {
list, ok := arg1.(traits.Lister) // validated by type-guards
if !ok { list := arg1.(traits.Lister)
return types.MaybeNoSuchOverloadErr(arg1) keys := arg2.(traits.Lister)
}
keys, ok := arg2.(traits.Lister)
if !ok {
return types.MaybeNoSuchOverloadErr(arg2)
}
sorted, err := sortListByAssociatedKeys(list, keys) sorted, err := sortListByAssociatedKeys(list, keys)
if err != nil { if err != nil {
return types.WrapErr(err) return types.WrapErr(err)
@ -498,8 +490,9 @@ func sortByMacro(meh cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (as
if targetKind != ast.ListKind && if targetKind != ast.ListKind &&
targetKind != ast.SelectKind && targetKind != ast.SelectKind &&
targetKind != ast.IdentKind && targetKind != ast.IdentKind &&
targetKind != ast.ComprehensionKind && targetKind != ast.CallKind { targetKind != ast.ComprehensionKind &&
return nil, meh.NewError(target.ID(), fmt.Sprintf("sortBy can only be applied to a list, identifier, comprehension, call or select expression")) targetKind != ast.CallKind {
return nil, meh.NewError(target.ID(), "sortBy can only be applied to a list, identifier, comprehension, call or select expression")
} }
mapCompr, err := parser.MakeMap(meh, meh.Copy(varIdent), args) mapCompr, err := parser.MakeMap(meh, meh.Copy(varIdent), args)

View File

@ -17,6 +17,7 @@ package ext
import ( import (
"errors" "errors"
"fmt" "fmt"
"math"
"reflect" "reflect"
"strings" "strings"
"time" "time"
@ -98,7 +99,9 @@ var (
func NativeTypes(args ...any) cel.EnvOption { func NativeTypes(args ...any) cel.EnvOption {
return func(env *cel.Env) (*cel.Env, error) { return func(env *cel.Env) (*cel.Env, error) {
nativeTypes := make([]any, 0, len(args)) nativeTypes := make([]any, 0, len(args))
tpOptions := nativeTypeOptions{} tpOptions := nativeTypeOptions{
version: math.MaxUint32,
}
for _, v := range args { for _, v := range args {
switch v := v.(type) { switch v := v.(type) {
@ -128,6 +131,14 @@ func NativeTypes(args ...any) cel.EnvOption {
// NativeTypesOption is a functional interface for configuring handling of native types. // NativeTypesOption is a functional interface for configuring handling of native types.
type NativeTypesOption func(*nativeTypeOptions) error type NativeTypesOption func(*nativeTypeOptions) error
// NativeTypesVersion sets the native types version support for native extensions functions.
func NativeTypesVersion(version uint32) NativeTypesOption {
return func(opts *nativeTypeOptions) error {
opts.version = version
return nil
}
}
// NativeTypesFieldNameHandler is a handler for mapping a reflect.StructField to a CEL field name. // NativeTypesFieldNameHandler is a handler for mapping a reflect.StructField to a CEL field name.
// This can be used to override the default Go struct field to CEL field name mapping. // This can be used to override the default Go struct field to CEL field name mapping.
type NativeTypesFieldNameHandler = func(field reflect.StructField) string type NativeTypesFieldNameHandler = func(field reflect.StructField) string
@ -158,6 +169,9 @@ type nativeTypeOptions struct {
// This is most commonly used for switching to parsing based off the struct field tag, // This is most commonly used for switching to parsing based off the struct field tag,
// such as "cel" or "json". // such as "cel" or "json".
fieldNameHandler NativeTypesFieldNameHandler fieldNameHandler NativeTypesFieldNameHandler
// version is the native types library version.
version uint32
} }
// ParseStructTags configures if native types field names should be overridable by CEL struct tags. // ParseStructTags configures if native types field names should be overridable by CEL struct tags.
@ -329,7 +343,7 @@ func (tp *nativeTypeProvider) NewValue(typeName string, fields map[string]ref.Va
} }
fieldVal, err := val.ConvertToNative(refFieldDef.Type) fieldVal, err := val.ConvertToNative(refFieldDef.Type)
if err != nil { if err != nil {
return types.NewErr(err.Error()) return types.NewErrFromString(err.Error())
} }
refField := refVal.FieldByIndex(refFieldDef.Index) refField := refVal.FieldByIndex(refFieldDef.Index)
refFieldVal := reflect.ValueOf(fieldVal) refFieldVal := reflect.ValueOf(fieldVal)
@ -436,7 +450,7 @@ func convertToCelType(refType reflect.Type) (*cel.Type, bool) {
func (tp *nativeTypeProvider) newNativeObject(val any, refValue reflect.Value) ref.Val { func (tp *nativeTypeProvider) newNativeObject(val any, refValue reflect.Value) ref.Val {
valType, err := newNativeType(tp.options.fieldNameHandler, refValue.Type()) valType, err := newNativeType(tp.options.fieldNameHandler, refValue.Type())
if err != nil { if err != nil {
return types.NewErr(err.Error()) return types.NewErrFromString(err.Error())
} }
return &nativeObj{ return &nativeObj{
Adapter: tp, Adapter: tp,

View File

@ -15,6 +15,8 @@
package ext package ext
import ( import (
"math"
"github.com/google/cel-go/cel" "github.com/google/cel-go/cel"
"github.com/google/cel-go/common/ast" "github.com/google/cel-go/common/ast"
) )
@ -49,8 +51,23 @@ import (
// Examples: // Examples:
// //
// proto.hasExt(msg, google.expr.proto2.test.int32_ext) // returns true || false // proto.hasExt(msg, google.expr.proto2.test.int32_ext) // returns true || false
func Protos() cel.EnvOption { func Protos(options ...ProtosOption) cel.EnvOption {
return cel.Lib(protoLib{}) l := &protoLib{version: math.MaxUint32}
for _, o := range options {
l = o(l)
}
return cel.Lib(l)
}
// ProtosOption declares a functional operator for configuring protobuf utilities.
type ProtosOption func(*protoLib) *protoLib
// ProtosVersion sets the library version for extensions for protobuf utilities.
func ProtosVersion(version uint32) ProtosOption {
return func(lib *protoLib) *protoLib {
lib.version = version
return lib
}
} }
var ( var (
@ -59,7 +76,9 @@ var (
getExtension = "getExt" getExtension = "getExt"
) )
type protoLib struct{} type protoLib struct {
version uint32
}
// LibraryName implements the SingletonLibrary interface method. // LibraryName implements the SingletonLibrary interface method.
func (protoLib) LibraryName() string { func (protoLib) LibraryName() string {

View File

@ -77,11 +77,28 @@ import (
// sets.intersects([1], []) // false // sets.intersects([1], []) // false
// sets.intersects([1], [1, 2]) // true // sets.intersects([1], [1, 2]) // true
// sets.intersects([[1], [2, 3]], [[1, 2], [2, 3.0]]) // true // sets.intersects([[1], [2, 3]], [[1, 2], [2, 3.0]]) // true
func Sets() cel.EnvOption { func Sets(options ...SetsOption) cel.EnvOption {
return cel.Lib(setsLib{}) l := &setsLib{}
for _, o := range options {
l = o(l)
}
return cel.Lib(l)
} }
type setsLib struct{} // SetsOption declares a functional operator for configuring set extensions.
type SetsOption func(*setsLib) *setsLib
// SetsVersion sets the library version for set extensions.
func SetsVersion(version uint32) SetsOption {
return func(lib *setsLib) *setsLib {
lib.version = version
return lib
}
}
type setsLib struct {
version uint32
}
// LibraryName implements the SingletonLibrary interface method. // LibraryName implements the SingletonLibrary interface method.
func (setsLib) LibraryName() string { func (setsLib) LibraryName() string {

View File

@ -156,6 +156,11 @@ type PartialActivation interface {
UnknownAttributePatterns() []*AttributePattern UnknownAttributePatterns() []*AttributePattern
} }
// partialActivationConverter indicates whether an Activation implementation supports conversion to a PartialActivation
type partialActivationConverter interface {
asPartialActivation() (PartialActivation, bool)
}
// partActivation is the default implementations of the PartialActivation interface. // partActivation is the default implementations of the PartialActivation interface.
type partActivation struct { type partActivation struct {
Activation Activation
@ -166,3 +171,20 @@ type partActivation struct {
func (a *partActivation) UnknownAttributePatterns() []*AttributePattern { func (a *partActivation) UnknownAttributePatterns() []*AttributePattern {
return a.unknowns return a.unknowns
} }
// asPartialActivation returns the partActivation as a PartialActivation interface.
func (a *partActivation) asPartialActivation() (PartialActivation, bool) {
return a, true
}
func asPartialActivation(vars Activation) (PartialActivation, bool) {
// Only internal activation instances may implement this interface
if pv, ok := vars.(partialActivationConverter); ok {
return pv.asPartialActivation()
}
// Since Activations may be hierarchical, test whether a parent converts to a PartialActivation
if vars.Parent() != nil {
return asPartialActivation(vars.Parent())
}
return nil, false
}

View File

@ -358,7 +358,7 @@ func (m *attributeMatcher) AddQualifier(qual Qualifier) (Attribute, error) {
func (m *attributeMatcher) Resolve(vars Activation) (any, error) { func (m *attributeMatcher) Resolve(vars Activation) (any, error) {
id := m.NamespacedAttribute.ID() id := m.NamespacedAttribute.ID()
// Bug in how partial activation is resolved, should search parents as well. // Bug in how partial activation is resolved, should search parents as well.
partial, isPartial := toPartialActivation(vars) partial, isPartial := asPartialActivation(vars)
if isPartial { if isPartial {
unk, err := m.fac.matchesUnknownPatterns( unk, err := m.fac.matchesUnknownPatterns(
partial, partial,
@ -384,14 +384,3 @@ func (m *attributeMatcher) Qualify(vars Activation, obj any) (any, error) {
func (m *attributeMatcher) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) { func (m *attributeMatcher) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) {
return attrQualifyIfPresent(m.fac, vars, obj, m, presenceOnly) return attrQualifyIfPresent(m.fac, vars, obj, m, presenceOnly)
} }
func toPartialActivation(vars Activation) (PartialActivation, bool) {
pv, ok := vars.(PartialActivation)
if ok {
return pv, true
}
if vars.Parent() != nil {
return toPartialActivation(vars.Parent())
}
return nil, false
}

View File

@ -762,6 +762,9 @@ func (fold *evalFold) Eval(ctx Activation) ref.Val {
defer releaseFolder(f) defer releaseFolder(f)
foldRange := fold.iterRange.Eval(ctx) foldRange := fold.iterRange.Eval(ctx)
if types.IsUnknownOrError(foldRange) {
return foldRange
}
if fold.iterVar2 != "" { if fold.iterVar2 != "" {
var foldable traits.Foldable var foldable traits.Foldable
switch r := foldRange.(type) { switch r := foldRange.(type) {
@ -1241,7 +1244,7 @@ func invalidOptionalElementInit(value ref.Val) ref.Val {
func newFolder(eval *evalFold, ctx Activation) *folder { func newFolder(eval *evalFold, ctx Activation) *folder {
f := folderPool.Get().(*folder) f := folderPool.Get().(*folder)
f.evalFold = eval f.evalFold = eval
f.Activation = ctx f.activation = ctx
return f return f
} }
@ -1262,7 +1265,7 @@ func releaseFolder(f *folder) {
// cel.bind or cel.@block. // cel.bind or cel.@block.
type folder struct { type folder struct {
*evalFold *evalFold
Activation activation Activation
// fold state objects. // fold state objects.
accuVal ref.Val accuVal ref.Val
@ -1290,7 +1293,7 @@ func (f *folder) foldIterable(iterable traits.Iterable) ref.Val {
// Update the accumulation value and check for eval interuption. // Update the accumulation value and check for eval interuption.
f.accuVal = f.step.Eval(f) f.accuVal = f.step.Eval(f)
f.initialized = true f.initialized = true
if f.interruptable && checkInterrupt(f.Activation) { if f.interruptable && checkInterrupt(f.activation) {
f.interrupted = true f.interrupted = true
return f.evalResult() return f.evalResult()
} }
@ -1316,7 +1319,7 @@ func (f *folder) FoldEntry(key, val any) bool {
// Update the accumulation value and check for eval interuption. // Update the accumulation value and check for eval interuption.
f.accuVal = f.step.Eval(f) f.accuVal = f.step.Eval(f)
f.initialized = true f.initialized = true
if f.interruptable && checkInterrupt(f.Activation) { if f.interruptable && checkInterrupt(f.activation) {
f.interrupted = true f.interrupted = true
return false return false
} }
@ -1330,7 +1333,7 @@ func (f *folder) ResolveName(name string) (any, bool) {
if name == f.accuVar { if name == f.accuVar {
if !f.initialized { if !f.initialized {
f.initialized = true f.initialized = true
initVal := f.accu.Eval(f.Activation) initVal := f.accu.Eval(f.activation)
if !f.exhaustive { if !f.exhaustive {
if l, isList := initVal.(traits.Lister); isList && l.Size() == types.IntZero { if l, isList := initVal.(traits.Lister); isList && l.Size() == types.IntZero {
initVal = types.NewMutableList(f.adapter) initVal = types.NewMutableList(f.adapter)
@ -1355,7 +1358,32 @@ func (f *folder) ResolveName(name string) (any, bool) {
return f.iterVar2Val, true return f.iterVar2Val, true
} }
} }
return f.Activation.ResolveName(name) return f.activation.ResolveName(name)
}
// Parent returns the activation embedded into the folder.
func (f *folder) Parent() Activation {
return f.activation
}
// UnknownAttributePatterns implements the PartialActivation interface returning the unknown patterns
// if they were provided to the input activation, or an empty set if the proxied activation is not partial.
func (f *folder) UnknownAttributePatterns() []*AttributePattern {
if pv, ok := f.activation.(partialActivationConverter); ok {
if partial, isPartial := pv.asPartialActivation(); isPartial {
return partial.UnknownAttributePatterns()
}
}
return []*AttributePattern{}
}
func (f *folder) asPartialActivation() (PartialActivation, bool) {
if pv, ok := f.activation.(partialActivationConverter); ok {
if _, isPartial := pv.asPartialActivation(); isPartial {
return f, true
}
}
return nil, false
} }
// evalResult computes the final result of the fold after all entries have been folded and accumulated. // evalResult computes the final result of the fold after all entries have been folded and accumulated.
@ -1381,7 +1409,7 @@ func (f *folder) evalResult() ref.Val {
// reset clears any state associated with folder evaluation. // reset clears any state associated with folder evaluation.
func (f *folder) reset() { func (f *folder) reset() {
f.evalFold = nil f.evalFold = nil
f.Activation = nil f.activation = nil
f.accuVal = nil f.accuVal = nil
f.iterVar1Val = nil f.iterVar1Val = nil
f.iterVar2Val = nil f.iterVar2Val = nil

View File

@ -506,7 +506,7 @@ func (p *planner) planCreateList(expr ast.Expr) (Interpretable, error) {
id: expr.ID(), id: expr.ID(),
elems: elems, elems: elems,
optionals: optionals, optionals: optionals,
hasOptionals: len(optionals) != 0, hasOptionals: len(optionalIndices) != 0,
adapter: p.adapter, adapter: p.adapter,
}, nil }, nil
} }
@ -518,6 +518,7 @@ func (p *planner) planCreateMap(expr ast.Expr) (Interpretable, error) {
optionals := make([]bool, len(entries)) optionals := make([]bool, len(entries))
keys := make([]Interpretable, len(entries)) keys := make([]Interpretable, len(entries))
vals := make([]Interpretable, len(entries)) vals := make([]Interpretable, len(entries))
hasOptionals := false
for i, e := range entries { for i, e := range entries {
entry := e.AsMapEntry() entry := e.AsMapEntry()
keyVal, err := p.Plan(entry.Key()) keyVal, err := p.Plan(entry.Key())
@ -532,13 +533,14 @@ func (p *planner) planCreateMap(expr ast.Expr) (Interpretable, error) {
} }
vals[i] = valVal vals[i] = valVal
optionals[i] = entry.IsOptional() optionals[i] = entry.IsOptional()
hasOptionals = hasOptionals || entry.IsOptional()
} }
return &evalMap{ return &evalMap{
id: expr.ID(), id: expr.ID(),
keys: keys, keys: keys,
vals: vals, vals: vals,
optionals: optionals, optionals: optionals,
hasOptionals: len(optionals) != 0, hasOptionals: hasOptionals,
adapter: p.adapter, adapter: p.adapter,
}, nil }, nil
} }
@ -554,6 +556,7 @@ func (p *planner) planCreateStruct(expr ast.Expr) (Interpretable, error) {
optionals := make([]bool, len(objFields)) optionals := make([]bool, len(objFields))
fields := make([]string, len(objFields)) fields := make([]string, len(objFields))
vals := make([]Interpretable, len(objFields)) vals := make([]Interpretable, len(objFields))
hasOptionals := false
for i, f := range objFields { for i, f := range objFields {
field := f.AsStructField() field := f.AsStructField()
fields[i] = field.Name() fields[i] = field.Name()
@ -563,6 +566,7 @@ func (p *planner) planCreateStruct(expr ast.Expr) (Interpretable, error) {
} }
vals[i] = val vals[i] = val
optionals[i] = field.IsOptional() optionals[i] = field.IsOptional()
hasOptionals = hasOptionals || field.IsOptional()
} }
return &evalObj{ return &evalObj{
id: expr.ID(), id: expr.ID(),
@ -570,7 +574,7 @@ func (p *planner) planCreateStruct(expr ast.Expr) (Interpretable, error) {
fields: fields, fields: fields,
vals: vals, vals: vals,
optionals: optionals, optionals: optionals,
hasOptionals: len(optionals) != 0, hasOptionals: hasOptionals,
provider: p.provider, provider: p.provider,
}, nil }, nil
} }

View File

@ -88,7 +88,7 @@ func PruneAst(expr ast.Expr, macroCalls map[int64]ast.Expr, state EvalState) *as
func (p *astPruner) maybeCreateLiteral(id int64, val ref.Val) (ast.Expr, bool) { func (p *astPruner) maybeCreateLiteral(id int64, val ref.Val) (ast.Expr, bool) {
switch v := val.(type) { switch v := val.(type) {
case types.Bool, types.Bytes, types.Double, types.Int, types.Null, types.String, types.Uint: case types.Bool, types.Bytes, types.Double, types.Int, types.Null, types.String, types.Uint, *types.Optional:
p.state.SetValue(id, val) p.state.SetValue(id, val)
return p.NewLiteral(id, val), true return p.NewLiteral(id, val), true
case types.Duration: case types.Duration:
@ -281,14 +281,30 @@ func (p *astPruner) prune(node ast.Expr) (ast.Expr, bool) {
} }
if macro, found := p.macroCalls[node.ID()]; found { if macro, found := p.macroCalls[node.ID()]; found {
// Ensure that intermediate values for the comprehension are cleared during pruning // Ensure that intermediate values for the comprehension are cleared during pruning
pruneMacroCall := node.Kind() != ast.UnspecifiedExprKind
if node.Kind() == ast.ComprehensionKind { if node.Kind() == ast.ComprehensionKind {
compre := node.AsComprehension() // Only prune cel.bind() calls since the variables of the comprehension are all
visit(macro, clearIterVarVisitor(compre.IterVar(), p.state)) // visible to the user, so there's no chance of an incorrect value being observed
// as a result of looking at intermediate computations within a comprehension.
pruneMacroCall = isCelBindMacro(macro)
} }
// prune the expression in terms of the macro call instead of the expanded form. if pruneMacroCall {
// prune the expression in terms of the macro call instead of the expanded form when
// dealing with macro call tracking references.
if newMacro, pruned := p.prune(macro); pruned { if newMacro, pruned := p.prune(macro); pruned {
p.macroCalls[node.ID()] = newMacro p.macroCalls[node.ID()] = newMacro
} }
} else {
// Otherwise just prune the macro target in keeping with the pruning behavior of the
// comprehensions later in the call graph.
macroCall := macro.AsCall()
if macroCall.Target() != nil {
if newTarget, pruned := p.prune(macroCall.Target()); pruned {
macro = p.NewMemberCall(macro.ID(), macroCall.FunctionName(), newTarget, macroCall.Args()...)
p.macroCalls[node.ID()] = macro
}
}
}
} }
// We have either an unknown/error value, or something we don't want to // We have either an unknown/error value, or something we don't want to
@ -421,6 +437,19 @@ func (p *astPruner) prune(node ast.Expr) (ast.Expr, bool) {
// the last iteration of the comprehension and not each step in the evaluation which // the last iteration of the comprehension and not each step in the evaluation which
// means that the any residuals computed in between might be inaccurate. // means that the any residuals computed in between might be inaccurate.
if newRange, pruned := p.maybePrune(compre.IterRange()); pruned { if newRange, pruned := p.maybePrune(compre.IterRange()); pruned {
if compre.HasIterVar2() {
return p.NewComprehensionTwoVar(
node.ID(),
newRange,
compre.IterVar(),
compre.IterVar2(),
compre.AccuVar(),
compre.AccuInit(),
compre.LoopCondition(),
compre.LoopStep(),
compre.Result(),
), true
}
return p.NewComprehension( return p.NewComprehension(
node.ID(), node.ID(),
newRange, newRange,
@ -468,16 +497,6 @@ func getMaxID(expr ast.Expr) int64 {
return maxID return maxID
} }
func clearIterVarVisitor(varName string, state EvalState) astVisitor {
return astVisitor{
visitExpr: func(e ast.Expr) {
if e.Kind() == ast.IdentKind && e.AsIdent() == varName {
state.SetValue(e.ID(), nil)
}
},
}
}
func maxIDVisitor(maxID *int64) astVisitor { func maxIDVisitor(maxID *int64) astVisitor {
return astVisitor{ return astVisitor{
visitExpr: func(e ast.Expr) { visitExpr: func(e ast.Expr) {
@ -541,3 +560,15 @@ func visit(expr ast.Expr, visitor astVisitor) {
} }
} }
} }
func isCelBindMacro(macro ast.Expr) bool {
if macro.Kind() != ast.CallKind {
return false
}
macroCall := macro.AsCall()
target := macroCall.Target()
return macroCall.FunctionName() == "bind" &&
macroCall.IsMemberFunction() &&
target.Kind() == ast.IdentKind &&
target.AsIdent() == "cel"
}

View File

@ -198,20 +198,20 @@ func (c *CostTracker) costCall(call InterpretableCall, args []ref.Val, result re
switch call.OverloadID() { switch call.OverloadID() {
// O(n) functions // O(n) functions
case overloads.StartsWithString, overloads.EndsWithString, overloads.StringToBytes, overloads.BytesToString, overloads.ExtQuoteString, overloads.ExtFormatString: case overloads.StartsWithString, overloads.EndsWithString, overloads.StringToBytes, overloads.BytesToString, overloads.ExtQuoteString, overloads.ExtFormatString:
cost += uint64(math.Ceil(float64(c.actualSize(args[0])) * common.StringTraversalCostFactor)) cost += uint64(math.Ceil(float64(actualSize(args[0])) * common.StringTraversalCostFactor))
case overloads.InList: case overloads.InList:
// If a list is composed entirely of constant values this is O(1), but we don't account for that here. // If a list is composed entirely of constant values this is O(1), but we don't account for that here.
// We just assume all list containment checks are O(n). // We just assume all list containment checks are O(n).
cost += c.actualSize(args[1]) cost += actualSize(args[1])
// O(min(m, n)) functions // O(min(m, n)) functions
case overloads.LessString, overloads.GreaterString, overloads.LessEqualsString, overloads.GreaterEqualsString, case overloads.LessString, overloads.GreaterString, overloads.LessEqualsString, overloads.GreaterEqualsString,
overloads.LessBytes, overloads.GreaterBytes, overloads.LessEqualsBytes, overloads.GreaterEqualsBytes, overloads.LessBytes, overloads.GreaterBytes, overloads.LessEqualsBytes, overloads.GreaterEqualsBytes,
overloads.Equals, overloads.NotEquals: overloads.Equals, overloads.NotEquals:
// When we check the equality of 2 scalar values (e.g. 2 integers, 2 floating-point numbers, 2 booleans etc.), // When we check the equality of 2 scalar values (e.g. 2 integers, 2 floating-point numbers, 2 booleans etc.),
// the CostTracker.actualSize() function by definition returns 1 for each operand, resulting in an overall cost // the CostTracker.ActualSize() function by definition returns 1 for each operand, resulting in an overall cost
// of 1. // of 1.
lhsSize := c.actualSize(args[0]) lhsSize := actualSize(args[0])
rhsSize := c.actualSize(args[1]) rhsSize := actualSize(args[1])
minSize := lhsSize minSize := lhsSize
if rhsSize < minSize { if rhsSize < minSize {
minSize = rhsSize minSize = rhsSize
@ -220,23 +220,23 @@ func (c *CostTracker) costCall(call InterpretableCall, args []ref.Val, result re
// O(m+n) functions // O(m+n) functions
case overloads.AddString, overloads.AddBytes: case overloads.AddString, overloads.AddBytes:
// In the worst case scenario, we would need to reallocate a new backing store and copy both operands over. // In the worst case scenario, we would need to reallocate a new backing store and copy both operands over.
cost += uint64(math.Ceil(float64(c.actualSize(args[0])+c.actualSize(args[1])) * common.StringTraversalCostFactor)) cost += uint64(math.Ceil(float64(actualSize(args[0])+actualSize(args[1])) * common.StringTraversalCostFactor))
// O(nm) functions // O(nm) functions
case overloads.MatchesString: case overloads.MatchesString:
// https://swtch.com/~rsc/regexp/regexp1.html applies to RE2 implementation supported by CEL // https://swtch.com/~rsc/regexp/regexp1.html applies to RE2 implementation supported by CEL
// Add one to string length for purposes of cost calculation to prevent product of string and regex to be 0 // Add one to string length for purposes of cost calculation to prevent product of string and regex to be 0
// in case where string is empty but regex is still expensive. // in case where string is empty but regex is still expensive.
strCost := uint64(math.Ceil((1.0 + float64(c.actualSize(args[0]))) * common.StringTraversalCostFactor)) strCost := uint64(math.Ceil((1.0 + float64(actualSize(args[0]))) * common.StringTraversalCostFactor))
// We don't know how many expressions are in the regex, just the string length (a huge // We don't know how many expressions are in the regex, just the string length (a huge
// improvement here would be to somehow get a count the number of expressions in the regex or // improvement here would be to somehow get a count the number of expressions in the regex or
// how many states are in the regex state machine and use that to measure regex cost). // how many states are in the regex state machine and use that to measure regex cost).
// For now, we're making a guess that each expression in a regex is typically at least 4 chars // For now, we're making a guess that each expression in a regex is typically at least 4 chars
// in length. // in length.
regexCost := uint64(math.Ceil(float64(c.actualSize(args[1])) * common.RegexStringLengthCostFactor)) regexCost := uint64(math.Ceil(float64(actualSize(args[1])) * common.RegexStringLengthCostFactor))
cost += strCost * regexCost cost += strCost * regexCost
case overloads.ContainsString: case overloads.ContainsString:
strCost := uint64(math.Ceil(float64(c.actualSize(args[0])) * common.StringTraversalCostFactor)) strCost := uint64(math.Ceil(float64(actualSize(args[0])) * common.StringTraversalCostFactor))
substrCost := uint64(math.Ceil(float64(c.actualSize(args[1])) * common.StringTraversalCostFactor)) substrCost := uint64(math.Ceil(float64(actualSize(args[1])) * common.StringTraversalCostFactor))
cost += strCost * substrCost cost += strCost * substrCost
default: default:
@ -253,11 +253,15 @@ func (c *CostTracker) costCall(call InterpretableCall, args []ref.Val, result re
return cost return cost
} }
// actualSize returns the size of value // actualSize returns the size of the value for all traits.Sizer values, a fixed size for all proto-based
func (c *CostTracker) actualSize(value ref.Val) uint64 { // objects, and a size of 1 for all other value types.
func actualSize(value ref.Val) uint64 {
if sz, ok := value.(traits.Sizer); ok { if sz, ok := value.(traits.Sizer); ok {
return uint64(sz.Size().(types.Int)) return uint64(sz.Size().(types.Int))
} }
if opt, ok := value.(*types.Optional); ok && opt.HasValue() {
return actualSize(opt.GetValue())
}
return 1 return 1
} }

View File

@ -15,8 +15,6 @@
package parser package parser
import ( import (
"fmt"
"github.com/google/cel-go/common" "github.com/google/cel-go/common"
) )
@ -31,11 +29,11 @@ func (e *parseErrors) errorCount() int {
} }
func (e *parseErrors) internalError(message string) { func (e *parseErrors) internalError(message string) {
e.errs.ReportErrorAtID(0, common.NoLocation, message) e.errs.ReportErrorAtID(0, common.NoLocation, "%s", message)
} }
func (e *parseErrors) syntaxError(l common.Location, message string) { func (e *parseErrors) syntaxError(l common.Location, message string) {
e.errs.ReportErrorAtID(0, l, fmt.Sprintf("Syntax error: %s", message)) e.errs.ReportErrorAtID(0, l, "Syntax error: %s", message)
} }
func (e *parseErrors) reportErrorAtID(id int64, l common.Location, message string, args ...any) { func (e *parseErrors) reportErrorAtID(id int64, l common.Location, message string, args ...any) {

View File

@ -52,13 +52,14 @@ unary
member member
: primary # PrimaryExpr : primary # PrimaryExpr
| member op='.' (opt='?')? id=IDENTIFIER # Select | member op='.' (opt='?')? id=escapeIdent # Select
| member op='.' id=IDENTIFIER open='(' args=exprList? ')' # MemberCall | member op='.' id=IDENTIFIER open='(' args=exprList? ')' # MemberCall
| member op='[' (opt='?')? index=expr ']' # Index | member op='[' (opt='?')? index=expr ']' # Index
; ;
primary primary
: leadingDot='.'? id=IDENTIFIER (op='(' args=exprList? ')')? # IdentOrGlobalCall : leadingDot='.'? id=IDENTIFIER # Ident
| leadingDot='.'? id=IDENTIFIER (op='(' args=exprList? ')') # GlobalCall
| '(' e=expr ')' # Nested | '(' e=expr ')' # Nested
| op='[' elems=listInit? ','? ']' # CreateList | op='[' elems=listInit? ','? ']' # CreateList
| op='{' entries=mapInitializerList? ','? '}' # CreateStruct | op='{' entries=mapInitializerList? ','? '}' # CreateStruct
@ -80,13 +81,18 @@ fieldInitializerList
; ;
optField optField
: (opt='?')? IDENTIFIER : (opt='?')? escapeIdent
; ;
mapInitializerList mapInitializerList
: keys+=optExpr cols+=':' values+=expr (',' keys+=optExpr cols+=':' values+=expr)* : keys+=optExpr cols+=':' values+=expr (',' keys+=optExpr cols+=':' values+=expr)*
; ;
escapeIdent
: id=IDENTIFIER # SimpleIdentifier
| id=ESC_IDENTIFIER # EscapedIdentifier
;
optExpr optExpr
: (opt='?')? e=expr : (opt='?')? e=expr
; ;
@ -198,3 +204,4 @@ STRING
BYTES : ('b' | 'B') STRING; BYTES : ('b' | 'B') STRING;
IDENTIFIER : (LETTER | '_') ( LETTER | DIGIT | '_')*; IDENTIFIER : (LETTER | '_') ( LETTER | DIGIT | '_')*;
ESC_IDENTIFIER : '`' (LETTER | DIGIT | '_' | '.' | '-' | '/' | ' ')+ '`';

File diff suppressed because one or more lines are too long

View File

@ -34,6 +34,7 @@ NUM_UINT=33
STRING=34 STRING=34
BYTES=35 BYTES=35
IDENTIFIER=36 IDENTIFIER=36
ESC_IDENTIFIER=37
'=='=1 '=='=1
'!='=2 '!='=2
'in'=3 'in'=3

File diff suppressed because one or more lines are too long

View File

@ -34,6 +34,7 @@ NUM_UINT=33
STRING=34 STRING=34
BYTES=35 BYTES=35
IDENTIFIER=36 IDENTIFIER=36
ESC_IDENTIFIER=37
'=='=1 '=='=1
'!='=2 '!='=2
'in'=3 'in'=3

View File

@ -1,4 +1,4 @@
// Code generated from /usr/local/google/home/tswadell/go/src/github.com/google/cel-go/parser/gen/CEL.g4 by ANTLR 4.13.1. DO NOT EDIT. // Code generated from /usr/local/google/home/jdtatum/github/cel-go/parser/gen/CEL.g4 by ANTLR 4.13.1. DO NOT EDIT.
package gen // CEL package gen // CEL
import "github.com/antlr4-go/antlr/v4" import "github.com/antlr4-go/antlr/v4"
@ -98,11 +98,17 @@ func (s *BaseCELListener) EnterIndex(ctx *IndexContext) {}
// ExitIndex is called when production Index is exited. // ExitIndex is called when production Index is exited.
func (s *BaseCELListener) ExitIndex(ctx *IndexContext) {} func (s *BaseCELListener) ExitIndex(ctx *IndexContext) {}
// EnterIdentOrGlobalCall is called when production IdentOrGlobalCall is entered. // EnterIdent is called when production Ident is entered.
func (s *BaseCELListener) EnterIdentOrGlobalCall(ctx *IdentOrGlobalCallContext) {} func (s *BaseCELListener) EnterIdent(ctx *IdentContext) {}
// ExitIdentOrGlobalCall is called when production IdentOrGlobalCall is exited. // ExitIdent is called when production Ident is exited.
func (s *BaseCELListener) ExitIdentOrGlobalCall(ctx *IdentOrGlobalCallContext) {} func (s *BaseCELListener) ExitIdent(ctx *IdentContext) {}
// EnterGlobalCall is called when production GlobalCall is entered.
func (s *BaseCELListener) EnterGlobalCall(ctx *GlobalCallContext) {}
// ExitGlobalCall is called when production GlobalCall is exited.
func (s *BaseCELListener) ExitGlobalCall(ctx *GlobalCallContext) {}
// EnterNested is called when production Nested is entered. // EnterNested is called when production Nested is entered.
func (s *BaseCELListener) EnterNested(ctx *NestedContext) {} func (s *BaseCELListener) EnterNested(ctx *NestedContext) {}
@ -164,6 +170,18 @@ func (s *BaseCELListener) EnterMapInitializerList(ctx *MapInitializerListContext
// ExitMapInitializerList is called when production mapInitializerList is exited. // ExitMapInitializerList is called when production mapInitializerList is exited.
func (s *BaseCELListener) ExitMapInitializerList(ctx *MapInitializerListContext) {} func (s *BaseCELListener) ExitMapInitializerList(ctx *MapInitializerListContext) {}
// EnterSimpleIdentifier is called when production SimpleIdentifier is entered.
func (s *BaseCELListener) EnterSimpleIdentifier(ctx *SimpleIdentifierContext) {}
// ExitSimpleIdentifier is called when production SimpleIdentifier is exited.
func (s *BaseCELListener) ExitSimpleIdentifier(ctx *SimpleIdentifierContext) {}
// EnterEscapedIdentifier is called when production EscapedIdentifier is entered.
func (s *BaseCELListener) EnterEscapedIdentifier(ctx *EscapedIdentifierContext) {}
// ExitEscapedIdentifier is called when production EscapedIdentifier is exited.
func (s *BaseCELListener) ExitEscapedIdentifier(ctx *EscapedIdentifierContext) {}
// EnterOptExpr is called when production optExpr is entered. // EnterOptExpr is called when production optExpr is entered.
func (s *BaseCELListener) EnterOptExpr(ctx *OptExprContext) {} func (s *BaseCELListener) EnterOptExpr(ctx *OptExprContext) {}

View File

@ -1,9 +1,8 @@
// Code generated from /usr/local/google/home/tswadell/go/src/github.com/google/cel-go/parser/gen/CEL.g4 by ANTLR 4.13.1. DO NOT EDIT. // Code generated from /usr/local/google/home/jdtatum/github/cel-go/parser/gen/CEL.g4 by ANTLR 4.13.1. DO NOT EDIT.
package gen // CEL package gen // CEL
import "github.com/antlr4-go/antlr/v4" import "github.com/antlr4-go/antlr/v4"
type BaseCELVisitor struct { type BaseCELVisitor struct {
*antlr.BaseParseTreeVisitor *antlr.BaseParseTreeVisitor
} }
@ -60,7 +59,11 @@ func (v *BaseCELVisitor) VisitIndex(ctx *IndexContext) interface{} {
return v.VisitChildren(ctx) return v.VisitChildren(ctx)
} }
func (v *BaseCELVisitor) VisitIdentOrGlobalCall(ctx *IdentOrGlobalCallContext) interface{} { func (v *BaseCELVisitor) VisitIdent(ctx *IdentContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseCELVisitor) VisitGlobalCall(ctx *GlobalCallContext) interface{} {
return v.VisitChildren(ctx) return v.VisitChildren(ctx)
} }
@ -104,6 +107,14 @@ func (v *BaseCELVisitor) VisitMapInitializerList(ctx *MapInitializerListContext)
return v.VisitChildren(ctx) return v.VisitChildren(ctx)
} }
func (v *BaseCELVisitor) VisitSimpleIdentifier(ctx *SimpleIdentifierContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseCELVisitor) VisitEscapedIdentifier(ctx *EscapedIdentifierContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseCELVisitor) VisitOptExpr(ctx *OptExprContext) interface{} { func (v *BaseCELVisitor) VisitOptExpr(ctx *OptExprContext) interface{} {
return v.VisitChildren(ctx) return v.VisitChildren(ctx)
} }

View File

@ -1,18 +1,19 @@
// Code generated from /usr/local/google/home/tswadell/go/src/github.com/google/cel-go/parser/gen/CEL.g4 by ANTLR 4.13.1. DO NOT EDIT. // Code generated from /usr/local/google/home/jdtatum/github/cel-go/parser/gen/CEL.g4 by ANTLR 4.13.1. DO NOT EDIT.
package gen package gen
import ( import (
"fmt" "fmt"
"github.com/antlr4-go/antlr/v4"
"sync" "sync"
"unicode" "unicode"
"github.com/antlr4-go/antlr/v4"
) )
// Suppress unused import error // Suppress unused import error
var _ = fmt.Printf var _ = fmt.Printf
var _ = sync.Once{} var _ = sync.Once{}
var _ = unicode.IsLetter var _ = unicode.IsLetter
type CELLexer struct { type CELLexer struct {
*antlr.BaseLexer *antlr.BaseLexer
channelNames []string channelNames []string
@ -52,7 +53,7 @@ func cellexerLexerInit() {
"RBRACE", "LPAREN", "RPAREN", "DOT", "COMMA", "MINUS", "EXCLAM", "QUESTIONMARK", "RBRACE", "LPAREN", "RPAREN", "DOT", "COMMA", "MINUS", "EXCLAM", "QUESTIONMARK",
"COLON", "PLUS", "STAR", "SLASH", "PERCENT", "CEL_TRUE", "CEL_FALSE", "COLON", "PLUS", "STAR", "SLASH", "PERCENT", "CEL_TRUE", "CEL_FALSE",
"NUL", "WHITESPACE", "COMMENT", "NUM_FLOAT", "NUM_INT", "NUM_UINT", "NUL", "WHITESPACE", "COMMENT", "NUM_FLOAT", "NUM_INT", "NUM_UINT",
"STRING", "BYTES", "IDENTIFIER", "STRING", "BYTES", "IDENTIFIER", "ESC_IDENTIFIER",
} }
staticData.RuleNames = []string{ staticData.RuleNames = []string{
"EQUALS", "NOT_EQUALS", "IN", "LESS", "LESS_EQUALS", "GREATER_EQUALS", "EQUALS", "NOT_EQUALS", "IN", "LESS", "LESS_EQUALS", "GREATER_EQUALS",
@ -62,11 +63,11 @@ func cellexerLexerInit() {
"NUL", "BACKSLASH", "LETTER", "DIGIT", "EXPONENT", "HEXDIGIT", "RAW", "NUL", "BACKSLASH", "LETTER", "DIGIT", "EXPONENT", "HEXDIGIT", "RAW",
"ESC_SEQ", "ESC_CHAR_SEQ", "ESC_OCT_SEQ", "ESC_BYTE_SEQ", "ESC_UNI_SEQ", "ESC_SEQ", "ESC_CHAR_SEQ", "ESC_OCT_SEQ", "ESC_BYTE_SEQ", "ESC_UNI_SEQ",
"WHITESPACE", "COMMENT", "NUM_FLOAT", "NUM_INT", "NUM_UINT", "STRING", "WHITESPACE", "COMMENT", "NUM_FLOAT", "NUM_INT", "NUM_UINT", "STRING",
"BYTES", "IDENTIFIER", "BYTES", "IDENTIFIER", "ESC_IDENTIFIER",
} }
staticData.PredictionContextCache = antlr.NewPredictionContextCache() staticData.PredictionContextCache = antlr.NewPredictionContextCache()
staticData.serializedATN = []int32{ staticData.serializedATN = []int32{
4, 0, 36, 423, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 0, 37, 435, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2,
4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2,
10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15,
7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7,
@ -75,195 +76,201 @@ func cellexerLexerInit() {
31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36,
7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7,
41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46,
1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 2, 47, 7, 47, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1,
1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 1, 7, 1,
1, 8, 1, 9, 1, 9, 1, 10, 1, 10, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 7, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 10, 1, 10, 1, 11, 1, 11, 1, 12, 1,
1, 14, 1, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 12, 1, 13, 1, 13, 1, 14, 1, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17,
19, 1, 19, 1, 20, 1, 20, 1, 21, 1, 21, 1, 22, 1, 22, 1, 23, 1, 23, 1, 24, 1, 18, 1, 18, 1, 19, 1, 19, 1, 20, 1, 20, 1, 21, 1, 21, 1, 22, 1, 22, 1,
1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 26, 1, 23, 1, 23, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26,
26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 29, 1, 29, 1, 26, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 28, 1,
1, 30, 1, 30, 1, 31, 1, 31, 3, 31, 177, 8, 31, 1, 31, 4, 31, 180, 8, 31, 28, 1, 29, 1, 29, 1, 30, 1, 30, 1, 31, 1, 31, 3, 31, 179, 8, 31, 1, 31,
11, 31, 12, 31, 181, 1, 32, 1, 32, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 1, 4, 31, 182, 8, 31, 11, 31, 12, 31, 183, 1, 32, 1, 32, 1, 33, 1, 33, 1,
34, 3, 34, 192, 8, 34, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 34, 1, 34, 1, 34, 1, 34, 3, 34, 194, 8, 34, 1, 35, 1, 35, 1, 35, 1, 36,
1, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 1, 1, 36, 1, 36, 1, 36, 1, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1,
38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38,
1, 38, 1, 38, 1, 38, 3, 38, 225, 8, 38, 1, 39, 4, 39, 228, 8, 39, 11, 39, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 227, 8, 38, 1, 39, 4,
12, 39, 229, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 5, 40, 238, 8, 40, 39, 230, 8, 39, 11, 39, 12, 39, 231, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40,
10, 40, 12, 40, 241, 9, 40, 1, 40, 1, 40, 1, 41, 4, 41, 246, 8, 41, 11, 1, 40, 5, 40, 240, 8, 40, 10, 40, 12, 40, 243, 9, 40, 1, 40, 1, 40, 1,
41, 12, 41, 247, 1, 41, 1, 41, 4, 41, 252, 8, 41, 11, 41, 12, 41, 253, 41, 4, 41, 248, 8, 41, 11, 41, 12, 41, 249, 1, 41, 1, 41, 4, 41, 254, 8,
1, 41, 3, 41, 257, 8, 41, 1, 41, 4, 41, 260, 8, 41, 11, 41, 12, 41, 261, 41, 11, 41, 12, 41, 255, 1, 41, 3, 41, 259, 8, 41, 1, 41, 4, 41, 262, 8,
1, 41, 1, 41, 1, 41, 1, 41, 4, 41, 268, 8, 41, 11, 41, 12, 41, 269, 1, 41, 11, 41, 12, 41, 263, 1, 41, 1, 41, 1, 41, 1, 41, 4, 41, 270, 8, 41,
41, 3, 41, 273, 8, 41, 3, 41, 275, 8, 41, 1, 42, 4, 42, 278, 8, 42, 11, 11, 41, 12, 41, 271, 1, 41, 3, 41, 275, 8, 41, 3, 41, 277, 8, 41, 1, 42,
42, 12, 42, 279, 1, 42, 1, 42, 1, 42, 1, 42, 4, 42, 286, 8, 42, 11, 42, 4, 42, 280, 8, 42, 11, 42, 12, 42, 281, 1, 42, 1, 42, 1, 42, 1, 42, 4,
12, 42, 287, 3, 42, 290, 8, 42, 1, 43, 4, 43, 293, 8, 43, 11, 43, 12, 43, 42, 288, 8, 42, 11, 42, 12, 42, 289, 3, 42, 292, 8, 42, 1, 43, 4, 43, 295,
294, 1, 43, 1, 43, 1, 43, 1, 43, 1, 43, 1, 43, 4, 43, 303, 8, 43, 11, 43, 8, 43, 11, 43, 12, 43, 296, 1, 43, 1, 43, 1, 43, 1, 43, 1, 43, 1, 43, 4,
12, 43, 304, 1, 43, 1, 43, 3, 43, 309, 8, 43, 1, 44, 1, 44, 1, 44, 5, 44, 43, 305, 8, 43, 11, 43, 12, 43, 306, 1, 43, 1, 43, 3, 43, 311, 8, 43, 1,
314, 8, 44, 10, 44, 12, 44, 317, 9, 44, 1, 44, 1, 44, 1, 44, 1, 44, 5, 44, 1, 44, 1, 44, 5, 44, 316, 8, 44, 10, 44, 12, 44, 319, 9, 44, 1, 44,
44, 323, 8, 44, 10, 44, 12, 44, 326, 9, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 5, 44, 325, 8, 44, 10, 44, 12, 44, 328, 9, 44, 1,
1, 44, 1, 44, 1, 44, 5, 44, 335, 8, 44, 10, 44, 12, 44, 338, 9, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 5, 44, 337, 8, 44, 10, 44,
44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 5, 44, 349, 12, 44, 340, 9, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1,
8, 44, 10, 44, 12, 44, 352, 9, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 5, 44, 351, 8, 44, 10, 44, 12, 44, 354, 9, 44, 1, 44, 1, 44,
44, 5, 44, 360, 8, 44, 10, 44, 12, 44, 363, 9, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 5, 44, 362, 8, 44, 10, 44, 12, 44, 365, 9,
1, 44, 1, 44, 5, 44, 370, 8, 44, 10, 44, 12, 44, 373, 9, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 5, 44, 372, 8, 44, 10, 44, 12, 44,
44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 5, 44, 383, 8, 44, 10, 44, 375, 9, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 5,
12, 44, 386, 9, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 385, 8, 44, 10, 44, 12, 44, 388, 9, 44, 1, 44, 1, 44, 1, 44, 1, 44,
44, 1, 44, 1, 44, 5, 44, 398, 8, 44, 10, 44, 12, 44, 401, 9, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 5, 44, 400, 8, 44, 10, 44, 12,
1, 44, 1, 44, 1, 44, 3, 44, 407, 8, 44, 1, 45, 1, 45, 1, 45, 1, 46, 1, 44, 403, 9, 44, 1, 44, 1, 44, 1, 44, 1, 44, 3, 44, 409, 8, 44, 1, 45, 1,
46, 3, 46, 414, 8, 46, 1, 46, 1, 46, 1, 46, 5, 46, 419, 8, 46, 10, 46, 45, 1, 45, 1, 46, 1, 46, 3, 46, 416, 8, 46, 1, 46, 1, 46, 1, 46, 5, 46,
12, 46, 422, 9, 46, 4, 336, 350, 384, 399, 0, 47, 1, 1, 3, 2, 5, 3, 7, 421, 8, 46, 10, 46, 12, 46, 424, 9, 46, 1, 47, 1, 47, 1, 47, 1, 47, 4,
4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 47, 430, 8, 47, 11, 47, 12, 47, 431, 1, 47, 1, 47, 4, 338, 352, 386, 401,
14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, 0, 48, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10,
23, 47, 24, 49, 25, 51, 26, 53, 27, 55, 28, 57, 0, 59, 0, 61, 0, 63, 0, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19,
65, 0, 67, 0, 69, 0, 71, 0, 73, 0, 75, 0, 77, 0, 79, 29, 81, 30, 83, 31, 39, 20, 41, 21, 43, 22, 45, 23, 47, 24, 49, 25, 51, 26, 53, 27, 55, 28,
85, 32, 87, 33, 89, 34, 91, 35, 93, 36, 1, 0, 16, 2, 0, 65, 90, 97, 122, 57, 0, 59, 0, 61, 0, 63, 0, 65, 0, 67, 0, 69, 0, 71, 0, 73, 0, 75, 0, 77,
2, 0, 69, 69, 101, 101, 2, 0, 43, 43, 45, 45, 3, 0, 48, 57, 65, 70, 97, 0, 79, 29, 81, 30, 83, 31, 85, 32, 87, 33, 89, 34, 91, 35, 93, 36, 95,
102, 2, 0, 82, 82, 114, 114, 10, 0, 34, 34, 39, 39, 63, 63, 92, 92, 96, 37, 1, 0, 17, 2, 0, 65, 90, 97, 122, 2, 0, 69, 69, 101, 101, 2, 0, 43,
98, 102, 102, 110, 110, 114, 114, 116, 116, 118, 118, 2, 0, 88, 88, 120, 43, 45, 45, 3, 0, 48, 57, 65, 70, 97, 102, 2, 0, 82, 82, 114, 114, 10,
120, 3, 0, 9, 10, 12, 13, 32, 32, 1, 0, 10, 10, 2, 0, 85, 85, 117, 117, 0, 34, 34, 39, 39, 63, 63, 92, 92, 96, 98, 102, 102, 110, 110, 114, 114,
4, 0, 10, 10, 13, 13, 34, 34, 92, 92, 4, 0, 10, 10, 13, 13, 39, 39, 92, 116, 116, 118, 118, 2, 0, 88, 88, 120, 120, 3, 0, 9, 10, 12, 13, 32, 32,
92, 1, 0, 92, 92, 3, 0, 10, 10, 13, 13, 34, 34, 3, 0, 10, 10, 13, 13, 39, 1, 0, 10, 10, 2, 0, 85, 85, 117, 117, 4, 0, 10, 10, 13, 13, 34, 34, 92,
39, 2, 0, 66, 66, 98, 98, 456, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 92, 4, 0, 10, 10, 13, 13, 39, 39, 92, 92, 1, 0, 92, 92, 3, 0, 10, 10, 13,
1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 13, 34, 34, 3, 0, 10, 10, 13, 13, 39, 39, 2, 0, 66, 66, 98, 98, 3, 0, 32,
1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 32, 45, 47, 95, 95, 471, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0,
21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1,
0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21,
0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0,
0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0,
0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 0, 79, 1, 0, 0, 0, 0, 81, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0,
1, 0, 0, 0, 0, 83, 1, 0, 0, 0, 0, 85, 1, 0, 0, 0, 0, 87, 1, 0, 0, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0,
89, 1, 0, 0, 0, 0, 91, 1, 0, 0, 0, 0, 93, 1, 0, 0, 0, 1, 95, 1, 0, 0, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 0, 79, 1, 0, 0, 0, 0, 81, 1,
3, 98, 1, 0, 0, 0, 5, 101, 1, 0, 0, 0, 7, 104, 1, 0, 0, 0, 9, 106, 1, 0, 0, 0, 0, 0, 83, 1, 0, 0, 0, 0, 85, 1, 0, 0, 0, 0, 87, 1, 0, 0, 0, 0, 89,
0, 0, 11, 109, 1, 0, 0, 0, 13, 112, 1, 0, 0, 0, 15, 114, 1, 0, 0, 0, 17, 1, 0, 0, 0, 0, 91, 1, 0, 0, 0, 0, 93, 1, 0, 0, 0, 0, 95, 1, 0, 0, 0, 1,
117, 1, 0, 0, 0, 19, 120, 1, 0, 0, 0, 21, 122, 1, 0, 0, 0, 23, 124, 1, 97, 1, 0, 0, 0, 3, 100, 1, 0, 0, 0, 5, 103, 1, 0, 0, 0, 7, 106, 1, 0, 0,
0, 0, 0, 25, 126, 1, 0, 0, 0, 27, 128, 1, 0, 0, 0, 29, 130, 1, 0, 0, 0, 0, 9, 108, 1, 0, 0, 0, 11, 111, 1, 0, 0, 0, 13, 114, 1, 0, 0, 0, 15, 116,
31, 132, 1, 0, 0, 0, 33, 134, 1, 0, 0, 0, 35, 136, 1, 0, 0, 0, 37, 138, 1, 0, 0, 0, 17, 119, 1, 0, 0, 0, 19, 122, 1, 0, 0, 0, 21, 124, 1, 0, 0,
1, 0, 0, 0, 39, 140, 1, 0, 0, 0, 41, 142, 1, 0, 0, 0, 43, 144, 1, 0, 0, 0, 23, 126, 1, 0, 0, 0, 25, 128, 1, 0, 0, 0, 27, 130, 1, 0, 0, 0, 29, 132,
0, 45, 146, 1, 0, 0, 0, 47, 148, 1, 0, 0, 0, 49, 150, 1, 0, 0, 0, 51, 152, 1, 0, 0, 0, 31, 134, 1, 0, 0, 0, 33, 136, 1, 0, 0, 0, 35, 138, 1, 0, 0,
1, 0, 0, 0, 53, 157, 1, 0, 0, 0, 55, 163, 1, 0, 0, 0, 57, 168, 1, 0, 0, 0, 37, 140, 1, 0, 0, 0, 39, 142, 1, 0, 0, 0, 41, 144, 1, 0, 0, 0, 43, 146,
0, 59, 170, 1, 0, 0, 0, 61, 172, 1, 0, 0, 0, 63, 174, 1, 0, 0, 0, 65, 183, 1, 0, 0, 0, 45, 148, 1, 0, 0, 0, 47, 150, 1, 0, 0, 0, 49, 152, 1, 0, 0,
1, 0, 0, 0, 67, 185, 1, 0, 0, 0, 69, 191, 1, 0, 0, 0, 71, 193, 1, 0, 0, 0, 51, 154, 1, 0, 0, 0, 53, 159, 1, 0, 0, 0, 55, 165, 1, 0, 0, 0, 57, 170,
0, 73, 196, 1, 0, 0, 0, 75, 201, 1, 0, 0, 0, 77, 224, 1, 0, 0, 0, 79, 227, 1, 0, 0, 0, 59, 172, 1, 0, 0, 0, 61, 174, 1, 0, 0, 0, 63, 176, 1, 0, 0,
1, 0, 0, 0, 81, 233, 1, 0, 0, 0, 83, 274, 1, 0, 0, 0, 85, 289, 1, 0, 0, 0, 65, 185, 1, 0, 0, 0, 67, 187, 1, 0, 0, 0, 69, 193, 1, 0, 0, 0, 71, 195,
0, 87, 308, 1, 0, 0, 0, 89, 406, 1, 0, 0, 0, 91, 408, 1, 0, 0, 0, 93, 413, 1, 0, 0, 0, 73, 198, 1, 0, 0, 0, 75, 203, 1, 0, 0, 0, 77, 226, 1, 0, 0,
1, 0, 0, 0, 95, 96, 5, 61, 0, 0, 96, 97, 5, 61, 0, 0, 97, 2, 1, 0, 0, 0, 0, 79, 229, 1, 0, 0, 0, 81, 235, 1, 0, 0, 0, 83, 276, 1, 0, 0, 0, 85, 291,
98, 99, 5, 33, 0, 0, 99, 100, 5, 61, 0, 0, 100, 4, 1, 0, 0, 0, 101, 102, 1, 0, 0, 0, 87, 310, 1, 0, 0, 0, 89, 408, 1, 0, 0, 0, 91, 410, 1, 0, 0,
5, 105, 0, 0, 102, 103, 5, 110, 0, 0, 103, 6, 1, 0, 0, 0, 104, 105, 5, 0, 93, 415, 1, 0, 0, 0, 95, 425, 1, 0, 0, 0, 97, 98, 5, 61, 0, 0, 98, 99,
60, 0, 0, 105, 8, 1, 0, 0, 0, 106, 107, 5, 60, 0, 0, 107, 108, 5, 61, 0, 5, 61, 0, 0, 99, 2, 1, 0, 0, 0, 100, 101, 5, 33, 0, 0, 101, 102, 5, 61,
0, 108, 10, 1, 0, 0, 0, 109, 110, 5, 62, 0, 0, 110, 111, 5, 61, 0, 0, 111, 0, 0, 102, 4, 1, 0, 0, 0, 103, 104, 5, 105, 0, 0, 104, 105, 5, 110, 0,
12, 1, 0, 0, 0, 112, 113, 5, 62, 0, 0, 113, 14, 1, 0, 0, 0, 114, 115, 5, 0, 105, 6, 1, 0, 0, 0, 106, 107, 5, 60, 0, 0, 107, 8, 1, 0, 0, 0, 108,
38, 0, 0, 115, 116, 5, 38, 0, 0, 116, 16, 1, 0, 0, 0, 117, 118, 5, 124, 109, 5, 60, 0, 0, 109, 110, 5, 61, 0, 0, 110, 10, 1, 0, 0, 0, 111, 112,
0, 0, 118, 119, 5, 124, 0, 0, 119, 18, 1, 0, 0, 0, 120, 121, 5, 91, 0, 5, 62, 0, 0, 112, 113, 5, 61, 0, 0, 113, 12, 1, 0, 0, 0, 114, 115, 5, 62,
0, 121, 20, 1, 0, 0, 0, 122, 123, 5, 93, 0, 0, 123, 22, 1, 0, 0, 0, 124, 0, 0, 115, 14, 1, 0, 0, 0, 116, 117, 5, 38, 0, 0, 117, 118, 5, 38, 0, 0,
125, 5, 123, 0, 0, 125, 24, 1, 0, 0, 0, 126, 127, 5, 125, 0, 0, 127, 26, 118, 16, 1, 0, 0, 0, 119, 120, 5, 124, 0, 0, 120, 121, 5, 124, 0, 0, 121,
1, 0, 0, 0, 128, 129, 5, 40, 0, 0, 129, 28, 1, 0, 0, 0, 130, 131, 5, 41, 18, 1, 0, 0, 0, 122, 123, 5, 91, 0, 0, 123, 20, 1, 0, 0, 0, 124, 125, 5,
0, 0, 131, 30, 1, 0, 0, 0, 132, 133, 5, 46, 0, 0, 133, 32, 1, 0, 0, 0, 93, 0, 0, 125, 22, 1, 0, 0, 0, 126, 127, 5, 123, 0, 0, 127, 24, 1, 0, 0,
134, 135, 5, 44, 0, 0, 135, 34, 1, 0, 0, 0, 136, 137, 5, 45, 0, 0, 137, 0, 128, 129, 5, 125, 0, 0, 129, 26, 1, 0, 0, 0, 130, 131, 5, 40, 0, 0,
36, 1, 0, 0, 0, 138, 139, 5, 33, 0, 0, 139, 38, 1, 0, 0, 0, 140, 141, 5, 131, 28, 1, 0, 0, 0, 132, 133, 5, 41, 0, 0, 133, 30, 1, 0, 0, 0, 134, 135,
63, 0, 0, 141, 40, 1, 0, 0, 0, 142, 143, 5, 58, 0, 0, 143, 42, 1, 0, 0, 5, 46, 0, 0, 135, 32, 1, 0, 0, 0, 136, 137, 5, 44, 0, 0, 137, 34, 1, 0,
0, 144, 145, 5, 43, 0, 0, 145, 44, 1, 0, 0, 0, 146, 147, 5, 42, 0, 0, 147, 0, 0, 138, 139, 5, 45, 0, 0, 139, 36, 1, 0, 0, 0, 140, 141, 5, 33, 0, 0,
46, 1, 0, 0, 0, 148, 149, 5, 47, 0, 0, 149, 48, 1, 0, 0, 0, 150, 151, 5, 141, 38, 1, 0, 0, 0, 142, 143, 5, 63, 0, 0, 143, 40, 1, 0, 0, 0, 144, 145,
37, 0, 0, 151, 50, 1, 0, 0, 0, 152, 153, 5, 116, 0, 0, 153, 154, 5, 114, 5, 58, 0, 0, 145, 42, 1, 0, 0, 0, 146, 147, 5, 43, 0, 0, 147, 44, 1, 0,
0, 0, 154, 155, 5, 117, 0, 0, 155, 156, 5, 101, 0, 0, 156, 52, 1, 0, 0, 0, 0, 148, 149, 5, 42, 0, 0, 149, 46, 1, 0, 0, 0, 150, 151, 5, 47, 0, 0,
0, 157, 158, 5, 102, 0, 0, 158, 159, 5, 97, 0, 0, 159, 160, 5, 108, 0, 151, 48, 1, 0, 0, 0, 152, 153, 5, 37, 0, 0, 153, 50, 1, 0, 0, 0, 154, 155,
0, 160, 161, 5, 115, 0, 0, 161, 162, 5, 101, 0, 0, 162, 54, 1, 0, 0, 0, 5, 116, 0, 0, 155, 156, 5, 114, 0, 0, 156, 157, 5, 117, 0, 0, 157, 158,
163, 164, 5, 110, 0, 0, 164, 165, 5, 117, 0, 0, 165, 166, 5, 108, 0, 0, 5, 101, 0, 0, 158, 52, 1, 0, 0, 0, 159, 160, 5, 102, 0, 0, 160, 161, 5,
166, 167, 5, 108, 0, 0, 167, 56, 1, 0, 0, 0, 168, 169, 5, 92, 0, 0, 169, 97, 0, 0, 161, 162, 5, 108, 0, 0, 162, 163, 5, 115, 0, 0, 163, 164, 5,
58, 1, 0, 0, 0, 170, 171, 7, 0, 0, 0, 171, 60, 1, 0, 0, 0, 172, 173, 2, 101, 0, 0, 164, 54, 1, 0, 0, 0, 165, 166, 5, 110, 0, 0, 166, 167, 5, 117,
48, 57, 0, 173, 62, 1, 0, 0, 0, 174, 176, 7, 1, 0, 0, 175, 177, 7, 2, 0, 0, 0, 167, 168, 5, 108, 0, 0, 168, 169, 5, 108, 0, 0, 169, 56, 1, 0, 0,
0, 176, 175, 1, 0, 0, 0, 176, 177, 1, 0, 0, 0, 177, 179, 1, 0, 0, 0, 178, 0, 170, 171, 5, 92, 0, 0, 171, 58, 1, 0, 0, 0, 172, 173, 7, 0, 0, 0, 173,
180, 3, 61, 30, 0, 179, 178, 1, 0, 0, 0, 180, 181, 1, 0, 0, 0, 181, 179, 60, 1, 0, 0, 0, 174, 175, 2, 48, 57, 0, 175, 62, 1, 0, 0, 0, 176, 178,
1, 0, 0, 0, 181, 182, 1, 0, 0, 0, 182, 64, 1, 0, 0, 0, 183, 184, 7, 3, 7, 1, 0, 0, 177, 179, 7, 2, 0, 0, 178, 177, 1, 0, 0, 0, 178, 179, 1, 0,
0, 0, 184, 66, 1, 0, 0, 0, 185, 186, 7, 4, 0, 0, 186, 68, 1, 0, 0, 0, 187, 0, 0, 179, 181, 1, 0, 0, 0, 180, 182, 3, 61, 30, 0, 181, 180, 1, 0, 0,
192, 3, 71, 35, 0, 188, 192, 3, 75, 37, 0, 189, 192, 3, 77, 38, 0, 190, 0, 182, 183, 1, 0, 0, 0, 183, 181, 1, 0, 0, 0, 183, 184, 1, 0, 0, 0, 184,
192, 3, 73, 36, 0, 191, 187, 1, 0, 0, 0, 191, 188, 1, 0, 0, 0, 191, 189, 64, 1, 0, 0, 0, 185, 186, 7, 3, 0, 0, 186, 66, 1, 0, 0, 0, 187, 188, 7,
1, 0, 0, 0, 191, 190, 1, 0, 0, 0, 192, 70, 1, 0, 0, 0, 193, 194, 3, 57, 4, 0, 0, 188, 68, 1, 0, 0, 0, 189, 194, 3, 71, 35, 0, 190, 194, 3, 75,
28, 0, 194, 195, 7, 5, 0, 0, 195, 72, 1, 0, 0, 0, 196, 197, 3, 57, 28, 37, 0, 191, 194, 3, 77, 38, 0, 192, 194, 3, 73, 36, 0, 193, 189, 1, 0,
0, 197, 198, 2, 48, 51, 0, 198, 199, 2, 48, 55, 0, 199, 200, 2, 48, 55, 0, 0, 193, 190, 1, 0, 0, 0, 193, 191, 1, 0, 0, 0, 193, 192, 1, 0, 0, 0,
0, 200, 74, 1, 0, 0, 0, 201, 202, 3, 57, 28, 0, 202, 203, 7, 6, 0, 0, 203, 194, 70, 1, 0, 0, 0, 195, 196, 3, 57, 28, 0, 196, 197, 7, 5, 0, 0, 197,
204, 3, 65, 32, 0, 204, 205, 3, 65, 32, 0, 205, 76, 1, 0, 0, 0, 206, 207, 72, 1, 0, 0, 0, 198, 199, 3, 57, 28, 0, 199, 200, 2, 48, 51, 0, 200, 201,
3, 57, 28, 0, 207, 208, 5, 117, 0, 0, 208, 209, 3, 65, 32, 0, 209, 210, 2, 48, 55, 0, 201, 202, 2, 48, 55, 0, 202, 74, 1, 0, 0, 0, 203, 204, 3,
3, 65, 32, 0, 210, 211, 3, 65, 32, 0, 211, 212, 3, 65, 32, 0, 212, 225, 57, 28, 0, 204, 205, 7, 6, 0, 0, 205, 206, 3, 65, 32, 0, 206, 207, 3, 65,
1, 0, 0, 0, 213, 214, 3, 57, 28, 0, 214, 215, 5, 85, 0, 0, 215, 216, 3, 32, 0, 207, 76, 1, 0, 0, 0, 208, 209, 3, 57, 28, 0, 209, 210, 5, 117, 0,
65, 32, 0, 216, 217, 3, 65, 32, 0, 217, 218, 3, 65, 32, 0, 218, 219, 3, 0, 210, 211, 3, 65, 32, 0, 211, 212, 3, 65, 32, 0, 212, 213, 3, 65, 32,
65, 32, 0, 219, 220, 3, 65, 32, 0, 220, 221, 3, 65, 32, 0, 221, 222, 3, 0, 213, 214, 3, 65, 32, 0, 214, 227, 1, 0, 0, 0, 215, 216, 3, 57, 28, 0,
65, 32, 0, 222, 223, 3, 65, 32, 0, 223, 225, 1, 0, 0, 0, 224, 206, 1, 0, 216, 217, 5, 85, 0, 0, 217, 218, 3, 65, 32, 0, 218, 219, 3, 65, 32, 0,
0, 0, 224, 213, 1, 0, 0, 0, 225, 78, 1, 0, 0, 0, 226, 228, 7, 7, 0, 0, 219, 220, 3, 65, 32, 0, 220, 221, 3, 65, 32, 0, 221, 222, 3, 65, 32, 0,
227, 226, 1, 0, 0, 0, 228, 229, 1, 0, 0, 0, 229, 227, 1, 0, 0, 0, 229, 222, 223, 3, 65, 32, 0, 223, 224, 3, 65, 32, 0, 224, 225, 3, 65, 32, 0,
230, 1, 0, 0, 0, 230, 231, 1, 0, 0, 0, 231, 232, 6, 39, 0, 0, 232, 80, 225, 227, 1, 0, 0, 0, 226, 208, 1, 0, 0, 0, 226, 215, 1, 0, 0, 0, 227,
1, 0, 0, 0, 233, 234, 5, 47, 0, 0, 234, 235, 5, 47, 0, 0, 235, 239, 1, 78, 1, 0, 0, 0, 228, 230, 7, 7, 0, 0, 229, 228, 1, 0, 0, 0, 230, 231, 1,
0, 0, 0, 236, 238, 8, 8, 0, 0, 237, 236, 1, 0, 0, 0, 238, 241, 1, 0, 0, 0, 0, 0, 231, 229, 1, 0, 0, 0, 231, 232, 1, 0, 0, 0, 232, 233, 1, 0, 0,
0, 239, 237, 1, 0, 0, 0, 239, 240, 1, 0, 0, 0, 240, 242, 1, 0, 0, 0, 241, 0, 233, 234, 6, 39, 0, 0, 234, 80, 1, 0, 0, 0, 235, 236, 5, 47, 0, 0, 236,
239, 1, 0, 0, 0, 242, 243, 6, 40, 0, 0, 243, 82, 1, 0, 0, 0, 244, 246, 237, 5, 47, 0, 0, 237, 241, 1, 0, 0, 0, 238, 240, 8, 8, 0, 0, 239, 238,
3, 61, 30, 0, 245, 244, 1, 0, 0, 0, 246, 247, 1, 0, 0, 0, 247, 245, 1, 1, 0, 0, 0, 240, 243, 1, 0, 0, 0, 241, 239, 1, 0, 0, 0, 241, 242, 1, 0,
0, 0, 0, 247, 248, 1, 0, 0, 0, 248, 249, 1, 0, 0, 0, 249, 251, 5, 46, 0, 0, 0, 242, 244, 1, 0, 0, 0, 243, 241, 1, 0, 0, 0, 244, 245, 6, 40, 0, 0,
0, 250, 252, 3, 61, 30, 0, 251, 250, 1, 0, 0, 0, 252, 253, 1, 0, 0, 0, 245, 82, 1, 0, 0, 0, 246, 248, 3, 61, 30, 0, 247, 246, 1, 0, 0, 0, 248,
253, 251, 1, 0, 0, 0, 253, 254, 1, 0, 0, 0, 254, 256, 1, 0, 0, 0, 255, 249, 1, 0, 0, 0, 249, 247, 1, 0, 0, 0, 249, 250, 1, 0, 0, 0, 250, 251,
257, 3, 63, 31, 0, 256, 255, 1, 0, 0, 0, 256, 257, 1, 0, 0, 0, 257, 275, 1, 0, 0, 0, 251, 253, 5, 46, 0, 0, 252, 254, 3, 61, 30, 0, 253, 252, 1,
1, 0, 0, 0, 258, 260, 3, 61, 30, 0, 259, 258, 1, 0, 0, 0, 260, 261, 1, 0, 0, 0, 254, 255, 1, 0, 0, 0, 255, 253, 1, 0, 0, 0, 255, 256, 1, 0, 0,
0, 0, 0, 261, 259, 1, 0, 0, 0, 261, 262, 1, 0, 0, 0, 262, 263, 1, 0, 0, 0, 256, 258, 1, 0, 0, 0, 257, 259, 3, 63, 31, 0, 258, 257, 1, 0, 0, 0,
0, 263, 264, 3, 63, 31, 0, 264, 275, 1, 0, 0, 0, 265, 267, 5, 46, 0, 0, 258, 259, 1, 0, 0, 0, 259, 277, 1, 0, 0, 0, 260, 262, 3, 61, 30, 0, 261,
266, 268, 3, 61, 30, 0, 267, 266, 1, 0, 0, 0, 268, 269, 1, 0, 0, 0, 269, 260, 1, 0, 0, 0, 262, 263, 1, 0, 0, 0, 263, 261, 1, 0, 0, 0, 263, 264,
267, 1, 0, 0, 0, 269, 270, 1, 0, 0, 0, 270, 272, 1, 0, 0, 0, 271, 273, 1, 0, 0, 0, 264, 265, 1, 0, 0, 0, 265, 266, 3, 63, 31, 0, 266, 277, 1,
3, 63, 31, 0, 272, 271, 1, 0, 0, 0, 272, 273, 1, 0, 0, 0, 273, 275, 1, 0, 0, 0, 267, 269, 5, 46, 0, 0, 268, 270, 3, 61, 30, 0, 269, 268, 1, 0,
0, 0, 0, 274, 245, 1, 0, 0, 0, 274, 259, 1, 0, 0, 0, 274, 265, 1, 0, 0, 0, 0, 270, 271, 1, 0, 0, 0, 271, 269, 1, 0, 0, 0, 271, 272, 1, 0, 0, 0,
0, 275, 84, 1, 0, 0, 0, 276, 278, 3, 61, 30, 0, 277, 276, 1, 0, 0, 0, 278, 272, 274, 1, 0, 0, 0, 273, 275, 3, 63, 31, 0, 274, 273, 1, 0, 0, 0, 274,
279, 1, 0, 0, 0, 279, 277, 1, 0, 0, 0, 279, 280, 1, 0, 0, 0, 280, 290, 275, 1, 0, 0, 0, 275, 277, 1, 0, 0, 0, 276, 247, 1, 0, 0, 0, 276, 261,
1, 0, 0, 0, 281, 282, 5, 48, 0, 0, 282, 283, 5, 120, 0, 0, 283, 285, 1, 1, 0, 0, 0, 276, 267, 1, 0, 0, 0, 277, 84, 1, 0, 0, 0, 278, 280, 3, 61,
0, 0, 0, 284, 286, 3, 65, 32, 0, 285, 284, 1, 0, 0, 0, 286, 287, 1, 0, 30, 0, 279, 278, 1, 0, 0, 0, 280, 281, 1, 0, 0, 0, 281, 279, 1, 0, 0, 0,
0, 0, 287, 285, 1, 0, 0, 0, 287, 288, 1, 0, 0, 0, 288, 290, 1, 0, 0, 0, 281, 282, 1, 0, 0, 0, 282, 292, 1, 0, 0, 0, 283, 284, 5, 48, 0, 0, 284,
289, 277, 1, 0, 0, 0, 289, 281, 1, 0, 0, 0, 290, 86, 1, 0, 0, 0, 291, 293, 285, 5, 120, 0, 0, 285, 287, 1, 0, 0, 0, 286, 288, 3, 65, 32, 0, 287, 286,
3, 61, 30, 0, 292, 291, 1, 0, 0, 0, 293, 294, 1, 0, 0, 0, 294, 292, 1, 1, 0, 0, 0, 288, 289, 1, 0, 0, 0, 289, 287, 1, 0, 0, 0, 289, 290, 1, 0,
0, 0, 0, 294, 295, 1, 0, 0, 0, 295, 296, 1, 0, 0, 0, 296, 297, 7, 9, 0, 0, 0, 290, 292, 1, 0, 0, 0, 291, 279, 1, 0, 0, 0, 291, 283, 1, 0, 0, 0,
0, 297, 309, 1, 0, 0, 0, 298, 299, 5, 48, 0, 0, 299, 300, 5, 120, 0, 0, 292, 86, 1, 0, 0, 0, 293, 295, 3, 61, 30, 0, 294, 293, 1, 0, 0, 0, 295,
300, 302, 1, 0, 0, 0, 301, 303, 3, 65, 32, 0, 302, 301, 1, 0, 0, 0, 303, 296, 1, 0, 0, 0, 296, 294, 1, 0, 0, 0, 296, 297, 1, 0, 0, 0, 297, 298,
304, 1, 0, 0, 0, 304, 302, 1, 0, 0, 0, 304, 305, 1, 0, 0, 0, 305, 306, 1, 0, 0, 0, 298, 299, 7, 9, 0, 0, 299, 311, 1, 0, 0, 0, 300, 301, 5, 48,
1, 0, 0, 0, 306, 307, 7, 9, 0, 0, 307, 309, 1, 0, 0, 0, 308, 292, 1, 0, 0, 0, 301, 302, 5, 120, 0, 0, 302, 304, 1, 0, 0, 0, 303, 305, 3, 65, 32,
0, 0, 308, 298, 1, 0, 0, 0, 309, 88, 1, 0, 0, 0, 310, 315, 5, 34, 0, 0, 0, 304, 303, 1, 0, 0, 0, 305, 306, 1, 0, 0, 0, 306, 304, 1, 0, 0, 0, 306,
311, 314, 3, 69, 34, 0, 312, 314, 8, 10, 0, 0, 313, 311, 1, 0, 0, 0, 313, 307, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 309, 7, 9, 0, 0, 309, 311,
312, 1, 0, 0, 0, 314, 317, 1, 0, 0, 0, 315, 313, 1, 0, 0, 0, 315, 316, 1, 0, 0, 0, 310, 294, 1, 0, 0, 0, 310, 300, 1, 0, 0, 0, 311, 88, 1, 0,
1, 0, 0, 0, 316, 318, 1, 0, 0, 0, 317, 315, 1, 0, 0, 0, 318, 407, 5, 34, 0, 0, 312, 317, 5, 34, 0, 0, 313, 316, 3, 69, 34, 0, 314, 316, 8, 10, 0,
0, 0, 319, 324, 5, 39, 0, 0, 320, 323, 3, 69, 34, 0, 321, 323, 8, 11, 0, 0, 315, 313, 1, 0, 0, 0, 315, 314, 1, 0, 0, 0, 316, 319, 1, 0, 0, 0, 317,
0, 322, 320, 1, 0, 0, 0, 322, 321, 1, 0, 0, 0, 323, 326, 1, 0, 0, 0, 324, 315, 1, 0, 0, 0, 317, 318, 1, 0, 0, 0, 318, 320, 1, 0, 0, 0, 319, 317,
322, 1, 0, 0, 0, 324, 325, 1, 0, 0, 0, 325, 327, 1, 0, 0, 0, 326, 324, 1, 0, 0, 0, 320, 409, 5, 34, 0, 0, 321, 326, 5, 39, 0, 0, 322, 325, 3,
1, 0, 0, 0, 327, 407, 5, 39, 0, 0, 328, 329, 5, 34, 0, 0, 329, 330, 5, 69, 34, 0, 323, 325, 8, 11, 0, 0, 324, 322, 1, 0, 0, 0, 324, 323, 1, 0,
34, 0, 0, 330, 331, 5, 34, 0, 0, 331, 336, 1, 0, 0, 0, 332, 335, 3, 69, 0, 0, 325, 328, 1, 0, 0, 0, 326, 324, 1, 0, 0, 0, 326, 327, 1, 0, 0, 0,
34, 0, 333, 335, 8, 12, 0, 0, 334, 332, 1, 0, 0, 0, 334, 333, 1, 0, 0, 327, 329, 1, 0, 0, 0, 328, 326, 1, 0, 0, 0, 329, 409, 5, 39, 0, 0, 330,
0, 335, 338, 1, 0, 0, 0, 336, 337, 1, 0, 0, 0, 336, 334, 1, 0, 0, 0, 337, 331, 5, 34, 0, 0, 331, 332, 5, 34, 0, 0, 332, 333, 5, 34, 0, 0, 333, 338,
339, 1, 0, 0, 0, 338, 336, 1, 0, 0, 0, 339, 340, 5, 34, 0, 0, 340, 341, 1, 0, 0, 0, 334, 337, 3, 69, 34, 0, 335, 337, 8, 12, 0, 0, 336, 334, 1,
5, 34, 0, 0, 341, 407, 5, 34, 0, 0, 342, 343, 5, 39, 0, 0, 343, 344, 5, 0, 0, 0, 336, 335, 1, 0, 0, 0, 337, 340, 1, 0, 0, 0, 338, 339, 1, 0, 0,
39, 0, 0, 344, 345, 5, 39, 0, 0, 345, 350, 1, 0, 0, 0, 346, 349, 3, 69, 0, 338, 336, 1, 0, 0, 0, 339, 341, 1, 0, 0, 0, 340, 338, 1, 0, 0, 0, 341,
34, 0, 347, 349, 8, 12, 0, 0, 348, 346, 1, 0, 0, 0, 348, 347, 1, 0, 0, 342, 5, 34, 0, 0, 342, 343, 5, 34, 0, 0, 343, 409, 5, 34, 0, 0, 344, 345,
0, 349, 352, 1, 0, 0, 0, 350, 351, 1, 0, 0, 0, 350, 348, 1, 0, 0, 0, 351, 5, 39, 0, 0, 345, 346, 5, 39, 0, 0, 346, 347, 5, 39, 0, 0, 347, 352, 1,
353, 1, 0, 0, 0, 352, 350, 1, 0, 0, 0, 353, 354, 5, 39, 0, 0, 354, 355, 0, 0, 0, 348, 351, 3, 69, 34, 0, 349, 351, 8, 12, 0, 0, 350, 348, 1, 0,
5, 39, 0, 0, 355, 407, 5, 39, 0, 0, 356, 357, 3, 67, 33, 0, 357, 361, 5, 0, 0, 350, 349, 1, 0, 0, 0, 351, 354, 1, 0, 0, 0, 352, 353, 1, 0, 0, 0,
34, 0, 0, 358, 360, 8, 13, 0, 0, 359, 358, 1, 0, 0, 0, 360, 363, 1, 0, 352, 350, 1, 0, 0, 0, 353, 355, 1, 0, 0, 0, 354, 352, 1, 0, 0, 0, 355,
0, 0, 361, 359, 1, 0, 0, 0, 361, 362, 1, 0, 0, 0, 362, 364, 1, 0, 0, 0, 356, 5, 39, 0, 0, 356, 357, 5, 39, 0, 0, 357, 409, 5, 39, 0, 0, 358, 359,
363, 361, 1, 0, 0, 0, 364, 365, 5, 34, 0, 0, 365, 407, 1, 0, 0, 0, 366, 3, 67, 33, 0, 359, 363, 5, 34, 0, 0, 360, 362, 8, 13, 0, 0, 361, 360, 1,
367, 3, 67, 33, 0, 367, 371, 5, 39, 0, 0, 368, 370, 8, 14, 0, 0, 369, 368, 0, 0, 0, 362, 365, 1, 0, 0, 0, 363, 361, 1, 0, 0, 0, 363, 364, 1, 0, 0,
1, 0, 0, 0, 370, 373, 1, 0, 0, 0, 371, 369, 1, 0, 0, 0, 371, 372, 1, 0, 0, 364, 366, 1, 0, 0, 0, 365, 363, 1, 0, 0, 0, 366, 367, 5, 34, 0, 0, 367,
0, 0, 372, 374, 1, 0, 0, 0, 373, 371, 1, 0, 0, 0, 374, 375, 5, 39, 0, 0, 409, 1, 0, 0, 0, 368, 369, 3, 67, 33, 0, 369, 373, 5, 39, 0, 0, 370, 372,
375, 407, 1, 0, 0, 0, 376, 377, 3, 67, 33, 0, 377, 378, 5, 34, 0, 0, 378, 8, 14, 0, 0, 371, 370, 1, 0, 0, 0, 372, 375, 1, 0, 0, 0, 373, 371, 1, 0,
379, 5, 34, 0, 0, 379, 380, 5, 34, 0, 0, 380, 384, 1, 0, 0, 0, 381, 383, 0, 0, 373, 374, 1, 0, 0, 0, 374, 376, 1, 0, 0, 0, 375, 373, 1, 0, 0, 0,
9, 0, 0, 0, 382, 381, 1, 0, 0, 0, 383, 386, 1, 0, 0, 0, 384, 385, 1, 0, 376, 377, 5, 39, 0, 0, 377, 409, 1, 0, 0, 0, 378, 379, 3, 67, 33, 0, 379,
0, 0, 384, 382, 1, 0, 0, 0, 385, 387, 1, 0, 0, 0, 386, 384, 1, 0, 0, 0, 380, 5, 34, 0, 0, 380, 381, 5, 34, 0, 0, 381, 382, 5, 34, 0, 0, 382, 386,
387, 388, 5, 34, 0, 0, 388, 389, 5, 34, 0, 0, 389, 390, 5, 34, 0, 0, 390, 1, 0, 0, 0, 383, 385, 9, 0, 0, 0, 384, 383, 1, 0, 0, 0, 385, 388, 1, 0,
407, 1, 0, 0, 0, 391, 392, 3, 67, 33, 0, 392, 393, 5, 39, 0, 0, 393, 394, 0, 0, 386, 387, 1, 0, 0, 0, 386, 384, 1, 0, 0, 0, 387, 389, 1, 0, 0, 0,
5, 39, 0, 0, 394, 395, 5, 39, 0, 0, 395, 399, 1, 0, 0, 0, 396, 398, 9, 388, 386, 1, 0, 0, 0, 389, 390, 5, 34, 0, 0, 390, 391, 5, 34, 0, 0, 391,
0, 0, 0, 397, 396, 1, 0, 0, 0, 398, 401, 1, 0, 0, 0, 399, 400, 1, 0, 0, 392, 5, 34, 0, 0, 392, 409, 1, 0, 0, 0, 393, 394, 3, 67, 33, 0, 394, 395,
0, 399, 397, 1, 0, 0, 0, 400, 402, 1, 0, 0, 0, 401, 399, 1, 0, 0, 0, 402, 5, 39, 0, 0, 395, 396, 5, 39, 0, 0, 396, 397, 5, 39, 0, 0, 397, 401, 1,
403, 5, 39, 0, 0, 403, 404, 5, 39, 0, 0, 404, 405, 5, 39, 0, 0, 405, 407, 0, 0, 0, 398, 400, 9, 0, 0, 0, 399, 398, 1, 0, 0, 0, 400, 403, 1, 0, 0,
1, 0, 0, 0, 406, 310, 1, 0, 0, 0, 406, 319, 1, 0, 0, 0, 406, 328, 1, 0, 0, 401, 402, 1, 0, 0, 0, 401, 399, 1, 0, 0, 0, 402, 404, 1, 0, 0, 0, 403,
0, 0, 406, 342, 1, 0, 0, 0, 406, 356, 1, 0, 0, 0, 406, 366, 1, 0, 0, 0, 401, 1, 0, 0, 0, 404, 405, 5, 39, 0, 0, 405, 406, 5, 39, 0, 0, 406, 407,
406, 376, 1, 0, 0, 0, 406, 391, 1, 0, 0, 0, 407, 90, 1, 0, 0, 0, 408, 409, 5, 39, 0, 0, 407, 409, 1, 0, 0, 0, 408, 312, 1, 0, 0, 0, 408, 321, 1, 0,
7, 15, 0, 0, 409, 410, 3, 89, 44, 0, 410, 92, 1, 0, 0, 0, 411, 414, 3, 0, 0, 408, 330, 1, 0, 0, 0, 408, 344, 1, 0, 0, 0, 408, 358, 1, 0, 0, 0,
59, 29, 0, 412, 414, 5, 95, 0, 0, 413, 411, 1, 0, 0, 0, 413, 412, 1, 0, 408, 368, 1, 0, 0, 0, 408, 378, 1, 0, 0, 0, 408, 393, 1, 0, 0, 0, 409,
0, 0, 414, 420, 1, 0, 0, 0, 415, 419, 3, 59, 29, 0, 416, 419, 3, 61, 30, 90, 1, 0, 0, 0, 410, 411, 7, 15, 0, 0, 411, 412, 3, 89, 44, 0, 412, 92,
0, 417, 419, 5, 95, 0, 0, 418, 415, 1, 0, 0, 0, 418, 416, 1, 0, 0, 0, 418, 1, 0, 0, 0, 413, 416, 3, 59, 29, 0, 414, 416, 5, 95, 0, 0, 415, 413, 1,
417, 1, 0, 0, 0, 419, 422, 1, 0, 0, 0, 420, 418, 1, 0, 0, 0, 420, 421, 0, 0, 0, 415, 414, 1, 0, 0, 0, 416, 422, 1, 0, 0, 0, 417, 421, 3, 59, 29,
1, 0, 0, 0, 421, 94, 1, 0, 0, 0, 422, 420, 1, 0, 0, 0, 36, 0, 176, 181, 0, 418, 421, 3, 61, 30, 0, 419, 421, 5, 95, 0, 0, 420, 417, 1, 0, 0, 0,
191, 224, 229, 239, 247, 253, 256, 261, 269, 272, 274, 279, 287, 289, 294, 420, 418, 1, 0, 0, 0, 420, 419, 1, 0, 0, 0, 421, 424, 1, 0, 0, 0, 422,
304, 308, 313, 315, 322, 324, 334, 336, 348, 350, 361, 371, 384, 399, 406, 420, 1, 0, 0, 0, 422, 423, 1, 0, 0, 0, 423, 94, 1, 0, 0, 0, 424, 422, 1,
413, 418, 420, 1, 0, 1, 0, 0, 0, 0, 425, 429, 5, 96, 0, 0, 426, 430, 3, 59, 29, 0, 427, 430, 3, 61,
30, 0, 428, 430, 7, 16, 0, 0, 429, 426, 1, 0, 0, 0, 429, 427, 1, 0, 0,
0, 429, 428, 1, 0, 0, 0, 430, 431, 1, 0, 0, 0, 431, 429, 1, 0, 0, 0, 431,
432, 1, 0, 0, 0, 432, 433, 1, 0, 0, 0, 433, 434, 5, 96, 0, 0, 434, 96,
1, 0, 0, 0, 38, 0, 178, 183, 193, 226, 231, 241, 249, 255, 258, 263, 271,
274, 276, 281, 289, 291, 296, 306, 310, 315, 317, 324, 326, 336, 338, 350,
352, 363, 373, 386, 401, 408, 415, 420, 422, 429, 431, 1, 0, 1, 0,
} }
deserializer := antlr.NewATNDeserializer(nil) deserializer := antlr.NewATNDeserializer(nil)
staticData.atn = deserializer.Deserialize(staticData.serializedATN) staticData.atn = deserializer.Deserialize(staticData.serializedATN)
@ -340,5 +347,5 @@ const (
CELLexerSTRING = 34 CELLexerSTRING = 34
CELLexerBYTES = 35 CELLexerBYTES = 35
CELLexerIDENTIFIER = 36 CELLexerIDENTIFIER = 36
CELLexerESC_IDENTIFIER = 37
) )

View File

@ -1,9 +1,8 @@
// Code generated from /usr/local/google/home/tswadell/go/src/github.com/google/cel-go/parser/gen/CEL.g4 by ANTLR 4.13.1. DO NOT EDIT. // Code generated from /usr/local/google/home/jdtatum/github/cel-go/parser/gen/CEL.g4 by ANTLR 4.13.1. DO NOT EDIT.
package gen // CEL package gen // CEL
import "github.com/antlr4-go/antlr/v4" import "github.com/antlr4-go/antlr/v4"
// CELListener is a complete listener for a parse tree produced by CELParser. // CELListener is a complete listener for a parse tree produced by CELParser.
type CELListener interface { type CELListener interface {
antlr.ParseTreeListener antlr.ParseTreeListener
@ -47,8 +46,11 @@ type CELListener interface {
// EnterIndex is called when entering the Index production. // EnterIndex is called when entering the Index production.
EnterIndex(c *IndexContext) EnterIndex(c *IndexContext)
// EnterIdentOrGlobalCall is called when entering the IdentOrGlobalCall production. // EnterIdent is called when entering the Ident production.
EnterIdentOrGlobalCall(c *IdentOrGlobalCallContext) EnterIdent(c *IdentContext)
// EnterGlobalCall is called when entering the GlobalCall production.
EnterGlobalCall(c *GlobalCallContext)
// EnterNested is called when entering the Nested production. // EnterNested is called when entering the Nested production.
EnterNested(c *NestedContext) EnterNested(c *NestedContext)
@ -80,6 +82,12 @@ type CELListener interface {
// EnterMapInitializerList is called when entering the mapInitializerList production. // EnterMapInitializerList is called when entering the mapInitializerList production.
EnterMapInitializerList(c *MapInitializerListContext) EnterMapInitializerList(c *MapInitializerListContext)
// EnterSimpleIdentifier is called when entering the SimpleIdentifier production.
EnterSimpleIdentifier(c *SimpleIdentifierContext)
// EnterEscapedIdentifier is called when entering the EscapedIdentifier production.
EnterEscapedIdentifier(c *EscapedIdentifierContext)
// EnterOptExpr is called when entering the optExpr production. // EnterOptExpr is called when entering the optExpr production.
EnterOptExpr(c *OptExprContext) EnterOptExpr(c *OptExprContext)
@ -146,8 +154,11 @@ type CELListener interface {
// ExitIndex is called when exiting the Index production. // ExitIndex is called when exiting the Index production.
ExitIndex(c *IndexContext) ExitIndex(c *IndexContext)
// ExitIdentOrGlobalCall is called when exiting the IdentOrGlobalCall production. // ExitIdent is called when exiting the Ident production.
ExitIdentOrGlobalCall(c *IdentOrGlobalCallContext) ExitIdent(c *IdentContext)
// ExitGlobalCall is called when exiting the GlobalCall production.
ExitGlobalCall(c *GlobalCallContext)
// ExitNested is called when exiting the Nested production. // ExitNested is called when exiting the Nested production.
ExitNested(c *NestedContext) ExitNested(c *NestedContext)
@ -179,6 +190,12 @@ type CELListener interface {
// ExitMapInitializerList is called when exiting the mapInitializerList production. // ExitMapInitializerList is called when exiting the mapInitializerList production.
ExitMapInitializerList(c *MapInitializerListContext) ExitMapInitializerList(c *MapInitializerListContext)
// ExitSimpleIdentifier is called when exiting the SimpleIdentifier production.
ExitSimpleIdentifier(c *SimpleIdentifierContext)
// ExitEscapedIdentifier is called when exiting the EscapedIdentifier production.
ExitEscapedIdentifier(c *EscapedIdentifierContext)
// ExitOptExpr is called when exiting the optExpr production. // ExitOptExpr is called when exiting the optExpr production.
ExitOptExpr(c *OptExprContext) ExitOptExpr(c *OptExprContext)

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,8 @@
// Code generated from /usr/local/google/home/tswadell/go/src/github.com/google/cel-go/parser/gen/CEL.g4 by ANTLR 4.13.1. DO NOT EDIT. // Code generated from /usr/local/google/home/jdtatum/github/cel-go/parser/gen/CEL.g4 by ANTLR 4.13.1. DO NOT EDIT.
package gen // CEL package gen // CEL
import "github.com/antlr4-go/antlr/v4" import "github.com/antlr4-go/antlr/v4"
// A complete Visitor for a parse tree produced by CELParser. // A complete Visitor for a parse tree produced by CELParser.
type CELVisitor interface { type CELVisitor interface {
antlr.ParseTreeVisitor antlr.ParseTreeVisitor
@ -47,8 +46,11 @@ type CELVisitor interface {
// Visit a parse tree produced by CELParser#Index. // Visit a parse tree produced by CELParser#Index.
VisitIndex(ctx *IndexContext) interface{} VisitIndex(ctx *IndexContext) interface{}
// Visit a parse tree produced by CELParser#IdentOrGlobalCall. // Visit a parse tree produced by CELParser#Ident.
VisitIdentOrGlobalCall(ctx *IdentOrGlobalCallContext) interface{} VisitIdent(ctx *IdentContext) interface{}
// Visit a parse tree produced by CELParser#GlobalCall.
VisitGlobalCall(ctx *GlobalCallContext) interface{}
// Visit a parse tree produced by CELParser#Nested. // Visit a parse tree produced by CELParser#Nested.
VisitNested(ctx *NestedContext) interface{} VisitNested(ctx *NestedContext) interface{}
@ -80,6 +82,12 @@ type CELVisitor interface {
// Visit a parse tree produced by CELParser#mapInitializerList. // Visit a parse tree produced by CELParser#mapInitializerList.
VisitMapInitializerList(ctx *MapInitializerListContext) interface{} VisitMapInitializerList(ctx *MapInitializerListContext) interface{}
// Visit a parse tree produced by CELParser#SimpleIdentifier.
VisitSimpleIdentifier(ctx *SimpleIdentifierContext) interface{}
// Visit a parse tree produced by CELParser#EscapedIdentifier.
VisitEscapedIdentifier(ctx *EscapedIdentifierContext) interface{}
// Visit a parse tree produced by CELParser#optExpr. // Visit a parse tree produced by CELParser#optExpr.
VisitOptExpr(ctx *OptExprContext) interface{} VisitOptExpr(ctx *OptExprContext) interface{}
@ -106,5 +114,4 @@ type CELVisitor interface {
// Visit a parse tree produced by CELParser#Null. // Visit a parse tree produced by CELParser#Null.
VisitNull(ctx *NullContext) interface{} VisitNull(ctx *NullContext) interface{}
} }

View File

@ -470,6 +470,11 @@ func (e *exprHelper) NewAccuIdent() ast.Expr {
return e.exprFactory.NewAccuIdent(e.nextMacroID()) return e.exprFactory.NewAccuIdent(e.nextMacroID())
} }
// AccuIdentName implements the ExprHelper interface method.
func (e *exprHelper) AccuIdentName() string {
return e.exprFactory.AccuIdentName()
}
// NewGlobalCall implements the ExprHelper interface method. // NewGlobalCall implements the ExprHelper interface method.
func (e *exprHelper) NewCall(function string, args ...ast.Expr) ast.Expr { func (e *exprHelper) NewCall(function string, args ...ast.Expr) ast.Expr {
return e.exprFactory.NewCall(e.nextMacroID(), function, args...) return e.exprFactory.NewCall(e.nextMacroID(), function, args...)

View File

@ -225,6 +225,9 @@ type ExprHelper interface {
// NewAccuIdent returns an accumulator identifier for use with comprehension results. // NewAccuIdent returns an accumulator identifier for use with comprehension results.
NewAccuIdent() ast.Expr NewAccuIdent() ast.Expr
// AccuIdentName returns the name of the accumulator identifier.
AccuIdentName() string
// NewCall creates a function call Expr value for a global (free) function. // NewCall creates a function call Expr value for a global (free) function.
NewCall(function string, args ...ast.Expr) ast.Expr NewCall(function string, args ...ast.Expr) ast.Expr
@ -259,8 +262,13 @@ var (
// ExistsOneMacro expands "range.exists_one(var, predicate)", which is true if for exactly one // ExistsOneMacro expands "range.exists_one(var, predicate)", which is true if for exactly one
// element in range the predicate holds. // element in range the predicate holds.
// Deprecated: Use ExistsOneMacroNew
ExistsOneMacro = NewReceiverMacro(operators.ExistsOne, 2, MakeExistsOne) ExistsOneMacro = NewReceiverMacro(operators.ExistsOne, 2, MakeExistsOne)
// ExistsOneMacroNew expands "range.existsOne(var, predicate)", which is true if for exactly one
// element in range the predicate holds.
ExistsOneMacroNew = NewReceiverMacro("existsOne", 2, MakeExistsOne)
// MapMacro expands "range.map(var, function)" into a comprehension which applies the function // MapMacro expands "range.map(var, function)" into a comprehension which applies the function
// to each element in the range to produce a new list. // to each element in the range to produce a new list.
MapMacro = NewReceiverMacro(operators.Map, 2, MakeMap) MapMacro = NewReceiverMacro(operators.Map, 2, MakeMap)
@ -280,6 +288,7 @@ var (
AllMacro, AllMacro,
ExistsMacro, ExistsMacro,
ExistsOneMacro, ExistsOneMacro,
ExistsOneMacroNew,
MapMacro, MapMacro,
MapFilterMacro, MapFilterMacro,
FilterMacro, FilterMacro,
@ -292,6 +301,11 @@ var (
// AccumulatorName is the traditional variable name assigned to the fold accumulator variable. // AccumulatorName is the traditional variable name assigned to the fold accumulator variable.
const AccumulatorName = "__result__" const AccumulatorName = "__result__"
// HiddenAccumulatorName is a proposed update to the default fold accumlator variable.
// @result is not normally accessible from source, preventing accidental or intentional collisions
// in user expressions.
const HiddenAccumulatorName = "@result"
type quantifierKind int type quantifierKind int
const ( const (
@ -336,6 +350,10 @@ func MakeMap(eh ExprHelper, target ast.Expr, args []ast.Expr) (ast.Expr, *common
if !found { if !found {
return nil, eh.NewError(args[0].ID(), "argument is not an identifier") return nil, eh.NewError(args[0].ID(), "argument is not an identifier")
} }
accu := eh.AccuIdentName()
if v == accu || v == AccumulatorName {
return nil, eh.NewError(args[0].ID(), "iteration variable overwrites accumulator variable")
}
var fn ast.Expr var fn ast.Expr
var filter ast.Expr var filter ast.Expr
@ -355,7 +373,7 @@ func MakeMap(eh ExprHelper, target ast.Expr, args []ast.Expr) (ast.Expr, *common
if filter != nil { if filter != nil {
step = eh.NewCall(operators.Conditional, filter, step, eh.NewAccuIdent()) step = eh.NewCall(operators.Conditional, filter, step, eh.NewAccuIdent())
} }
return eh.NewComprehension(target, v, AccumulatorName, init, condition, step, eh.NewAccuIdent()), nil return eh.NewComprehension(target, v, accu, init, condition, step, eh.NewAccuIdent()), nil
} }
// MakeFilter expands the input call arguments into a comprehension which produces a list which contains // MakeFilter expands the input call arguments into a comprehension which produces a list which contains
@ -366,13 +384,17 @@ func MakeFilter(eh ExprHelper, target ast.Expr, args []ast.Expr) (ast.Expr, *com
if !found { if !found {
return nil, eh.NewError(args[0].ID(), "argument is not an identifier") return nil, eh.NewError(args[0].ID(), "argument is not an identifier")
} }
accu := eh.AccuIdentName()
if v == accu || v == AccumulatorName {
return nil, eh.NewError(args[0].ID(), "iteration variable overwrites accumulator variable")
}
filter := args[1] filter := args[1]
init := eh.NewList() init := eh.NewList()
condition := eh.NewLiteral(types.True) condition := eh.NewLiteral(types.True)
step := eh.NewCall(operators.Add, eh.NewAccuIdent(), eh.NewList(args[0])) step := eh.NewCall(operators.Add, eh.NewAccuIdent(), eh.NewList(args[0]))
step = eh.NewCall(operators.Conditional, filter, step, eh.NewAccuIdent()) step = eh.NewCall(operators.Conditional, filter, step, eh.NewAccuIdent())
return eh.NewComprehension(target, v, AccumulatorName, init, condition, step, eh.NewAccuIdent()), nil return eh.NewComprehension(target, v, accu, init, condition, step, eh.NewAccuIdent()), nil
} }
// MakeHas expands the input call arguments into a presence test, e.g. has(<operand>.field) // MakeHas expands the input call arguments into a presence test, e.g. has(<operand>.field)
@ -389,6 +411,10 @@ func makeQuantifier(kind quantifierKind, eh ExprHelper, target ast.Expr, args []
if !found { if !found {
return nil, eh.NewError(args[0].ID(), "argument must be a simple name") return nil, eh.NewError(args[0].ID(), "argument must be a simple name")
} }
accu := eh.AccuIdentName()
if v == accu || v == AccumulatorName {
return nil, eh.NewError(args[0].ID(), "iteration variable overwrites accumulator variable")
}
var init ast.Expr var init ast.Expr
var condition ast.Expr var condition ast.Expr
@ -416,7 +442,7 @@ func makeQuantifier(kind quantifierKind, eh ExprHelper, target ast.Expr, args []
default: default:
return nil, eh.NewError(args[0].ID(), fmt.Sprintf("unrecognized quantifier '%v'", kind)) return nil, eh.NewError(args[0].ID(), fmt.Sprintf("unrecognized quantifier '%v'", kind))
} }
return eh.NewComprehension(target, v, AccumulatorName, init, condition, step, result), nil return eh.NewComprehension(target, v, accu, init, condition, step, result), nil
} }
func extractIdent(e ast.Expr) (string, bool) { func extractIdent(e ast.Expr) (string, bool) {

View File

@ -26,6 +26,8 @@ type options struct {
populateMacroCalls bool populateMacroCalls bool
enableOptionalSyntax bool enableOptionalSyntax bool
enableVariadicOperatorASTs bool enableVariadicOperatorASTs bool
enableIdentEscapeSyntax bool
enableHiddenAccumulatorName bool
} }
// Option configures the behavior of the parser. // Option configures the behavior of the parser.
@ -127,6 +129,27 @@ func EnableOptionalSyntax(optionalSyntax bool) Option {
} }
} }
// EnableIdentEscapeSyntax enables backtick (`) escaped field identifiers. This
// supports extended types of characters in identifiers, e.g. foo.`baz-bar`.
func EnableIdentEscapeSyntax(enableIdentEscapeSyntax bool) Option {
return func(opts *options) error {
opts.enableIdentEscapeSyntax = enableIdentEscapeSyntax
return nil
}
}
// EnableHiddenAccumulatorName uses an accumulator variable name that is not a
// normally accessible identifier in source for comprehension macros. Compatibility notes:
// with this option enabled, a parsed AST would be semantically the same as if disabled, but would
// have different internal identifiers in any of the built-in comprehension sub-expressions. When
// disabled, it is possible but almost certainly a logic error to access the accumulator variable.
func EnableHiddenAccumulatorName(enabled bool) Option {
return func(opts *options) error {
opts.enableHiddenAccumulatorName = enabled
return nil
}
}
// EnableVariadicOperatorASTs enables a compact representation of chained like-kind commutative // EnableVariadicOperatorASTs enables a compact representation of chained like-kind commutative
// operators. e.g. `a || b || c || d` -> `call(op='||', args=[a, b, c, d])` // operators. e.g. `a || b || c || d` -> `call(op='||', args=[a, b, c, d])`
// //

View File

@ -17,6 +17,7 @@
package parser package parser
import ( import (
"errors"
"fmt" "fmt"
"regexp" "regexp"
"strconv" "strconv"
@ -40,6 +41,7 @@ type Parser struct {
// NewParser builds and returns a new Parser using the provided options. // NewParser builds and returns a new Parser using the provided options.
func NewParser(opts ...Option) (*Parser, error) { func NewParser(opts ...Option) (*Parser, error) {
p := &Parser{} p := &Parser{}
p.enableHiddenAccumulatorName = true
for _, opt := range opts { for _, opt := range opts {
if err := opt(&p.options); err != nil { if err := opt(&p.options); err != nil {
return nil, err return nil, err
@ -88,7 +90,11 @@ func mustNewParser(opts ...Option) *Parser {
// Parse parses the expression represented by source and returns the result. // Parse parses the expression represented by source and returns the result.
func (p *Parser) Parse(source common.Source) (*ast.AST, *common.Errors) { func (p *Parser) Parse(source common.Source) (*ast.AST, *common.Errors) {
errs := common.NewErrors(source) errs := common.NewErrors(source)
fac := ast.NewExprFactory() accu := AccumulatorName
if p.enableHiddenAccumulatorName {
accu = HiddenAccumulatorName
}
fac := ast.NewExprFactoryWithAccumulator(accu)
impl := parser{ impl := parser{
errors: &parseErrors{errs}, errors: &parseErrors{errs},
exprFactory: fac, exprFactory: fac,
@ -101,6 +107,7 @@ func (p *Parser) Parse(source common.Source) (*ast.AST, *common.Errors) {
populateMacroCalls: p.populateMacroCalls, populateMacroCalls: p.populateMacroCalls,
enableOptionalSyntax: p.enableOptionalSyntax, enableOptionalSyntax: p.enableOptionalSyntax,
enableVariadicOperatorASTs: p.enableVariadicOperatorASTs, enableVariadicOperatorASTs: p.enableVariadicOperatorASTs,
enableIdentEscapeSyntax: p.enableIdentEscapeSyntax,
} }
buf, ok := source.(runes.Buffer) buf, ok := source.(runes.Buffer)
if !ok { if !ok {
@ -143,6 +150,27 @@ var reservedIds = map[string]struct{}{
"while": {}, "while": {},
} }
func unescapeIdent(in string) (string, error) {
if len(in) <= 2 {
return "", errors.New("invalid escaped identifier: underflow")
}
return in[1 : len(in)-1], nil
}
// normalizeIdent returns the interpreted identifier.
func (p *parser) normalizeIdent(ctx gen.IEscapeIdentContext) (string, error) {
switch ident := ctx.(type) {
case *gen.SimpleIdentifierContext:
return ident.GetId().GetText(), nil
case *gen.EscapedIdentifierContext:
if !p.enableIdentEscapeSyntax {
return "", errors.New("unsupported syntax: '`'")
}
return unescapeIdent(ident.GetId().GetText())
}
return "", errors.New("unsupported ident kind")
}
// Parse converts a source input a parsed expression. // Parse converts a source input a parsed expression.
// This function calls ParseWithMacros with AllMacros. // This function calls ParseWithMacros with AllMacros.
// //
@ -296,6 +324,7 @@ type parser struct {
populateMacroCalls bool populateMacroCalls bool
enableOptionalSyntax bool enableOptionalSyntax bool
enableVariadicOperatorASTs bool enableVariadicOperatorASTs bool
enableIdentEscapeSyntax bool
} }
var _ gen.CELVisitor = (*parser)(nil) var _ gen.CELVisitor = (*parser)(nil)
@ -369,8 +398,10 @@ func (p *parser) Visit(tree antlr.ParseTree) any {
return out return out
case *gen.LogicalNotContext: case *gen.LogicalNotContext:
return p.VisitLogicalNot(tree) return p.VisitLogicalNot(tree)
case *gen.IdentOrGlobalCallContext: case *gen.IdentContext:
return p.VisitIdentOrGlobalCall(tree) return p.VisitIdent(tree)
case *gen.GlobalCallContext:
return p.VisitGlobalCall(tree)
case *gen.SelectContext: case *gen.SelectContext:
p.checkAndIncrementRecursionDepth() p.checkAndIncrementRecursionDepth()
out := p.VisitSelect(tree) out := p.VisitSelect(tree)
@ -538,7 +569,10 @@ func (p *parser) VisitSelect(ctx *gen.SelectContext) any {
if ctx.GetId() == nil || ctx.GetOp() == nil { if ctx.GetId() == nil || ctx.GetOp() == nil {
return p.helper.newExpr(ctx) return p.helper.newExpr(ctx)
} }
id := ctx.GetId().GetText() id, err := p.normalizeIdent(ctx.GetId())
if err != nil {
p.reportError(ctx.GetId(), "%v", err)
}
if ctx.GetOpt() != nil { if ctx.GetOpt() != nil {
if !p.enableOptionalSyntax { if !p.enableOptionalSyntax {
return p.reportError(ctx.GetOp(), "unsupported syntax '.?'") return p.reportError(ctx.GetOp(), "unsupported syntax '.?'")
@ -622,12 +656,14 @@ func (p *parser) VisitIFieldInitializerList(ctx gen.IFieldInitializerListContext
p.reportError(optField, "unsupported syntax '?'") p.reportError(optField, "unsupported syntax '?'")
continue continue
} }
// The field may be empty due to a prior error. // The field may be empty due to a prior error.
id := optField.IDENTIFIER() fieldName, err := p.normalizeIdent(optField.EscapeIdent())
if id == nil { if err != nil {
return []ast.EntryExpr{} p.reportError(ctx, "%v", err)
continue
} }
fieldName := id.GetText()
value := p.Visit(vals[i]).(ast.Expr) value := p.Visit(vals[i]).(ast.Expr)
field := p.helper.newObjectField(initID, fieldName, value, optional) field := p.helper.newObjectField(initID, fieldName, value, optional)
result[i] = field result[i] = field
@ -635,8 +671,27 @@ func (p *parser) VisitIFieldInitializerList(ctx gen.IFieldInitializerListContext
return result return result
} }
// Visit a parse tree produced by CELParser#IdentOrGlobalCall. // Visit a parse tree produced by CELParser#Ident.
func (p *parser) VisitIdentOrGlobalCall(ctx *gen.IdentOrGlobalCallContext) any { func (p *parser) VisitIdent(ctx *gen.IdentContext) any {
identName := ""
if ctx.GetLeadingDot() != nil {
identName = "."
}
// Handle the error case where no valid identifier is specified.
if ctx.GetId() == nil {
return p.helper.newExpr(ctx)
}
// Handle reserved identifiers.
id := ctx.GetId().GetText()
if _, ok := reservedIds[id]; ok {
return p.reportError(ctx, "reserved identifier: %s", id)
}
identName += id
return p.helper.newIdent(ctx.GetId(), identName)
}
// Visit a parse tree produced by CELParser#GlobalCallContext.
func (p *parser) VisitGlobalCall(ctx *gen.GlobalCallContext) any {
identName := "" identName := ""
if ctx.GetLeadingDot() != nil { if ctx.GetLeadingDot() != nil {
identName = "." identName = "."
@ -651,11 +706,9 @@ func (p *parser) VisitIdentOrGlobalCall(ctx *gen.IdentOrGlobalCallContext) any {
return p.reportError(ctx, "reserved identifier: %s", id) return p.reportError(ctx, "reserved identifier: %s", id)
} }
identName += id identName += id
if ctx.GetOp() != nil {
opID := p.helper.id(ctx.GetOp()) opID := p.helper.id(ctx.GetOp())
return p.globalCallOrMacro(opID, identName, p.visitExprList(ctx.GetArgs())...) return p.globalCallOrMacro(opID, identName, p.visitExprList(ctx.GetArgs())...)
}
return p.helper.newIdent(ctx.GetId(), identName)
} }
// Visit a parse tree produced by CELParser#CreateList. // Visit a parse tree produced by CELParser#CreateList.
@ -756,7 +809,7 @@ func (p *parser) VisitDouble(ctx *gen.DoubleContext) any {
// Visit a parse tree produced by CELParser#String. // Visit a parse tree produced by CELParser#String.
func (p *parser) VisitString(ctx *gen.StringContext) any { func (p *parser) VisitString(ctx *gen.StringContext) any {
s := p.unquote(ctx, ctx.GetText(), false) s := p.unquote(ctx, ctx.GetTok().GetText(), false)
return p.helper.newLiteralString(ctx, s) return p.helper.newLiteralString(ctx, s)
} }
@ -922,7 +975,7 @@ func (p *parser) expandMacro(exprID int64, function string, target ast.Expr, arg
loc = p.helper.getLocation(exprID) loc = p.helper.getLocation(exprID)
} }
p.helper.deleteID(exprID) p.helper.deleteID(exprID)
return p.reportError(loc, err.Message), true return p.reportError(loc, "%s", err.Message), true
} }
// A nil value from the macro indicates that the macro implementation decided that // A nil value from the macro indicates that the macro implementation decided that
// an expansion should not be performed. // an expansion should not be performed.

View File

@ -15,7 +15,7 @@
package parser package parser
import ( import (
"fmt" "errors"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
) )
@ -30,7 +30,7 @@ func unescape(value string, isBytes bool) (string, error) {
// Nothing to unescape / decode. // Nothing to unescape / decode.
if n < 2 { if n < 2 {
return value, fmt.Errorf("unable to unescape string") return value, errors.New("unable to unescape string")
} }
// Raw string preceded by the 'r|R' prefix. // Raw string preceded by the 'r|R' prefix.
@ -43,7 +43,7 @@ func unescape(value string, isBytes bool) (string, error) {
// Quoted string of some form, must have same first and last char. // Quoted string of some form, must have same first and last char.
if value[0] != value[n-1] || (value[0] != '"' && value[0] != '\'') { if value[0] != value[n-1] || (value[0] != '"' && value[0] != '\'') {
return value, fmt.Errorf("unable to unescape string") return value, errors.New("unable to unescape string")
} }
// Normalize the multi-line CEL string representation to a standard // Normalize the multi-line CEL string representation to a standard
@ -51,12 +51,12 @@ func unescape(value string, isBytes bool) (string, error) {
if n >= 6 { if n >= 6 {
if strings.HasPrefix(value, "'''") { if strings.HasPrefix(value, "'''") {
if !strings.HasSuffix(value, "'''") { if !strings.HasSuffix(value, "'''") {
return value, fmt.Errorf("unable to unescape string") return value, errors.New("unable to unescape string")
} }
value = "\"" + value[3:n-3] + "\"" value = "\"" + value[3:n-3] + "\""
} else if strings.HasPrefix(value, `"""`) { } else if strings.HasPrefix(value, `"""`) {
if !strings.HasSuffix(value, `"""`) { if !strings.HasSuffix(value, `"""`) {
return value, fmt.Errorf("unable to unescape string") return value, errors.New("unable to unescape string")
} }
value = "\"" + value[3:n-3] + "\"" value = "\"" + value[3:n-3] + "\""
} }
@ -113,7 +113,7 @@ func unescapeChar(s string, isBytes bool) (value rune, encode bool, tail string,
// 2. Last character is the start of an escape sequence. // 2. Last character is the start of an escape sequence.
if len(s) <= 1 { if len(s) <= 1 {
err = fmt.Errorf("unable to unescape string, found '\\' as last character") err = errors.New("unable to unescape string, found '\\' as last character")
return return
} }
@ -157,32 +157,32 @@ func unescapeChar(s string, isBytes bool) (value rune, encode bool, tail string,
case 'u': case 'u':
n = 4 n = 4
if isBytes { if isBytes {
err = fmt.Errorf("unable to unescape string") err = errors.New("unable to unescape string")
return return
} }
case 'U': case 'U':
n = 8 n = 8
if isBytes { if isBytes {
err = fmt.Errorf("unable to unescape string") err = errors.New("unable to unescape string")
return return
} }
} }
var v rune var v rune
if len(s) < n { if len(s) < n {
err = fmt.Errorf("unable to unescape string") err = errors.New("unable to unescape string")
return return
} }
for j := 0; j < n; j++ { for j := 0; j < n; j++ {
x, ok := unhex(s[j]) x, ok := unhex(s[j])
if !ok { if !ok {
err = fmt.Errorf("unable to unescape string") err = errors.New("unable to unescape string")
return return
} }
v = v<<4 | x v = v<<4 | x
} }
s = s[n:] s = s[n:]
if !isBytes && v > utf8.MaxRune { if !isBytes && !utf8.ValidRune(v) {
err = fmt.Errorf("unable to unescape string") err = errors.New("invalid unicode code point")
return return
} }
value = v value = v
@ -190,20 +190,20 @@ func unescapeChar(s string, isBytes bool) (value rune, encode bool, tail string,
// 5. Octal escape sequences, must be three digits \[0-3][0-7][0-7] // 5. Octal escape sequences, must be three digits \[0-3][0-7][0-7]
case '0', '1', '2', '3': case '0', '1', '2', '3':
if len(s) < 2 { if len(s) < 2 {
err = fmt.Errorf("unable to unescape octal sequence in string") err = errors.New("unable to unescape octal sequence in string")
return return
} }
v := rune(c - '0') v := rune(c - '0')
for j := 0; j < 2; j++ { for j := 0; j < 2; j++ {
x := s[j] x := s[j]
if x < '0' || x > '7' { if x < '0' || x > '7' {
err = fmt.Errorf("unable to unescape octal sequence in string") err = errors.New("unable to unescape octal sequence in string")
return return
} }
v = v*8 + rune(x-'0') v = v*8 + rune(x-'0')
} }
if !isBytes && v > utf8.MaxRune { if !isBytes && !utf8.ValidRune(v) {
err = fmt.Errorf("unable to unescape string") err = errors.New("invalid unicode code point")
return return
} }
value = v value = v
@ -212,7 +212,7 @@ func unescapeChar(s string, isBytes bool) (value rune, encode bool, tail string,
// Unknown escape sequence. // Unknown escape sequence.
default: default:
err = fmt.Errorf("unable to unescape string") err = errors.New("unable to unescape string")
} }
tail = s tail = s

View File

@ -17,12 +17,14 @@ package parser
import ( import (
"errors" "errors"
"fmt" "fmt"
"regexp"
"strconv" "strconv"
"strings" "strings"
"github.com/google/cel-go/common/ast" "github.com/google/cel-go/common/ast"
"github.com/google/cel-go/common/operators" "github.com/google/cel-go/common/operators"
"github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
) )
// Unparse takes an input expression and source position information and generates a human-readable // Unparse takes an input expression and source position information and generates a human-readable
@ -65,6 +67,15 @@ func Unparse(expr ast.Expr, info *ast.SourceInfo, opts ...UnparserOption) (strin
return un.str.String(), nil return un.str.String(), nil
} }
var identifierPartPattern *regexp.Regexp = regexp.MustCompile(`^[A-Za-z_][0-9A-Za-z_]*$`)
func maybeQuoteField(field string) string {
if !identifierPartPattern.MatchString(field) || field == "in" {
return "`" + field + "`"
}
return field
}
// unparser visits an expression to reconstruct a human-readable string from an AST. // unparser visits an expression to reconstruct a human-readable string from an AST.
type unparser struct { type unparser struct {
str strings.Builder str strings.Builder
@ -263,8 +274,17 @@ func (un *unparser) visitCallUnary(expr ast.Expr) error {
return un.visitMaybeNested(args[0], nested) return un.visitMaybeNested(args[0], nested)
} }
func (un *unparser) visitConst(expr ast.Expr) error { func (un *unparser) visitConstVal(val ref.Val) error {
val := expr.AsLiteral() optional := false
if optVal, ok := val.(*types.Optional); ok {
if !optVal.HasValue() {
un.str.WriteString("optional.none()")
return nil
}
optional = true
un.str.WriteString("optional.of(")
val = optVal.GetValue()
}
switch val := val.(type) { switch val := val.(type) {
case types.Bool: case types.Bool:
un.str.WriteString(strconv.FormatBool(bool(val))) un.str.WriteString(strconv.FormatBool(bool(val)))
@ -293,7 +313,21 @@ func (un *unparser) visitConst(expr ast.Expr) error {
ui := strconv.FormatUint(uint64(val), 10) ui := strconv.FormatUint(uint64(val), 10)
un.str.WriteString(ui) un.str.WriteString(ui)
un.str.WriteString("u") un.str.WriteString("u")
case *types.Optional:
if err := un.visitConstVal(val); err != nil {
return err
}
default: default:
return errors.New("unsupported constant")
}
if optional {
un.str.WriteString(")")
}
return nil
}
func (un *unparser) visitConst(expr ast.Expr) error {
val := expr.AsLiteral()
if err := un.visitConstVal(val); err != nil {
return fmt.Errorf("unsupported constant: %v", expr) return fmt.Errorf("unsupported constant: %v", expr)
} }
return nil return nil
@ -352,7 +386,7 @@ func (un *unparser) visitSelectInternal(operand ast.Expr, testOnly bool, op stri
return err return err
} }
un.str.WriteString(op) un.str.WriteString(op)
un.str.WriteString(field) un.str.WriteString(maybeQuoteField(field))
if testOnly { if testOnly {
un.str.WriteString(")") un.str.WriteString(")")
} }
@ -370,7 +404,7 @@ func (un *unparser) visitStructMsg(expr ast.Expr) error {
if field.IsOptional() { if field.IsOptional() {
un.str.WriteString("?") un.str.WriteString("?")
} }
un.str.WriteString(f) un.str.WriteString(maybeQuoteField(f))
un.str.WriteString(": ") un.str.WriteString(": ")
v := field.Value() v := field.Value()
err := un.visit(v) err := un.visit(v)

View File

@ -1,10 +0,0 @@
language: go
go:
- 1.11.x
- 1.12.x
- 1.13.x
- master
script:
- go test -cover

View File

@ -1,67 +0,0 @@
# How to contribute #
We'd love to accept your patches and contributions to this project. There are
just a few small guidelines you need to follow.
## Contributor License Agreement ##
Contributions to any Google project must be accompanied by a Contributor
License Agreement. This is not a copyright **assignment**, it simply gives
Google permission to use and redistribute your contributions as part of the
project.
* If you are an individual writing original source code and you're sure you
own the intellectual property, then you'll need to sign an [individual
CLA][].
* If you work for a company that wants to allow you to contribute your work,
then you'll need to sign a [corporate CLA][].
You generally only need to submit a CLA once, so if you've already submitted
one (even if it was for a different project), you probably don't need to do it
again.
[individual CLA]: https://developers.google.com/open-source/cla/individual
[corporate CLA]: https://developers.google.com/open-source/cla/corporate
## Submitting a patch ##
1. It's generally best to start by opening a new issue describing the bug or
feature you're intending to fix. Even if you think it's relatively minor,
it's helpful to know what people are working on. Mention in the initial
issue that you are planning to work on that bug or feature so that it can
be assigned to you.
1. Follow the normal process of [forking][] the project, and setup a new
branch to work in. It's important that each group of changes be done in
separate branches in order to ensure that a pull request only includes the
commits related to that bug or feature.
1. Go makes it very simple to ensure properly formatted code, so always run
`go fmt` on your code before committing it. You should also run
[golint][] over your code. As noted in the [golint readme][], it's not
strictly necessary that your code be completely "lint-free", but this will
help you find common style issues.
1. Any significant changes should almost always be accompanied by tests. The
project already has good test coverage, so look at some of the existing
tests if you're unsure how to go about it. [gocov][] and [gocov-html][]
are invaluable tools for seeing which parts of your code aren't being
exercised by your tests.
1. Do your best to have [well-formed commit messages][] for each change.
This provides consistency throughout the project, and ensures that commit
messages are able to be formatted properly by various git tools.
1. Finally, push the commits to your fork and submit a [pull request][].
[forking]: https://help.github.com/articles/fork-a-repo
[golint]: https://github.com/golang/lint
[golint readme]: https://github.com/golang/lint/blob/master/README
[gocov]: https://github.com/axw/gocov
[gocov-html]: https://github.com/matm/gocov-html
[well-formed commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
[squash]: http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits
[pull request]: https://help.github.com/articles/creating-a-pull-request

View File

@ -1,18 +0,0 @@
/*
Copyright 2014 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package fuzz is a library for populating go objects with random values.
package fuzz

View File

@ -1,605 +0,0 @@
/*
Copyright 2014 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fuzz
import (
"fmt"
"math/rand"
"reflect"
"regexp"
"time"
"github.com/google/gofuzz/bytesource"
"strings"
)
// fuzzFuncMap is a map from a type to a fuzzFunc that handles that type.
type fuzzFuncMap map[reflect.Type]reflect.Value
// Fuzzer knows how to fill any object with random fields.
type Fuzzer struct {
fuzzFuncs fuzzFuncMap
defaultFuzzFuncs fuzzFuncMap
r *rand.Rand
nilChance float64
minElements int
maxElements int
maxDepth int
skipFieldPatterns []*regexp.Regexp
}
// New returns a new Fuzzer. Customize your Fuzzer further by calling Funcs,
// RandSource, NilChance, or NumElements in any order.
func New() *Fuzzer {
return NewWithSeed(time.Now().UnixNano())
}
func NewWithSeed(seed int64) *Fuzzer {
f := &Fuzzer{
defaultFuzzFuncs: fuzzFuncMap{
reflect.TypeOf(&time.Time{}): reflect.ValueOf(fuzzTime),
},
fuzzFuncs: fuzzFuncMap{},
r: rand.New(rand.NewSource(seed)),
nilChance: .2,
minElements: 1,
maxElements: 10,
maxDepth: 100,
}
return f
}
// NewFromGoFuzz is a helper function that enables using gofuzz (this
// project) with go-fuzz (https://github.com/dvyukov/go-fuzz) for continuous
// fuzzing. Essentially, it enables translating the fuzzing bytes from
// go-fuzz to any Go object using this library.
//
// This implementation promises a constant translation from a given slice of
// bytes to the fuzzed objects. This promise will remain over future
// versions of Go and of this library.
//
// Note: the returned Fuzzer should not be shared between multiple goroutines,
// as its deterministic output will no longer be available.
//
// Example: use go-fuzz to test the function `MyFunc(int)` in the package
// `mypackage`. Add the file: "mypacakge_fuzz.go" with the content:
//
// // +build gofuzz
// package mypacakge
// import fuzz "github.com/google/gofuzz"
// func Fuzz(data []byte) int {
// var i int
// fuzz.NewFromGoFuzz(data).Fuzz(&i)
// MyFunc(i)
// return 0
// }
func NewFromGoFuzz(data []byte) *Fuzzer {
return New().RandSource(bytesource.New(data))
}
// Funcs adds each entry in fuzzFuncs as a custom fuzzing function.
//
// Each entry in fuzzFuncs must be a function taking two parameters.
// The first parameter must be a pointer or map. It is the variable that
// function will fill with random data. The second parameter must be a
// fuzz.Continue, which will provide a source of randomness and a way
// to automatically continue fuzzing smaller pieces of the first parameter.
//
// These functions are called sensibly, e.g., if you wanted custom string
// fuzzing, the function `func(s *string, c fuzz.Continue)` would get
// called and passed the address of strings. Maps and pointers will always
// be made/new'd for you, ignoring the NilChange option. For slices, it
// doesn't make much sense to pre-create them--Fuzzer doesn't know how
// long you want your slice--so take a pointer to a slice, and make it
// yourself. (If you don't want your map/pointer type pre-made, take a
// pointer to it, and make it yourself.) See the examples for a range of
// custom functions.
func (f *Fuzzer) Funcs(fuzzFuncs ...interface{}) *Fuzzer {
for i := range fuzzFuncs {
v := reflect.ValueOf(fuzzFuncs[i])
if v.Kind() != reflect.Func {
panic("Need only funcs!")
}
t := v.Type()
if t.NumIn() != 2 || t.NumOut() != 0 {
panic("Need 2 in and 0 out params!")
}
argT := t.In(0)
switch argT.Kind() {
case reflect.Ptr, reflect.Map:
default:
panic("fuzzFunc must take pointer or map type")
}
if t.In(1) != reflect.TypeOf(Continue{}) {
panic("fuzzFunc's second parameter must be type fuzz.Continue")
}
f.fuzzFuncs[argT] = v
}
return f
}
// RandSource causes f to get values from the given source of randomness.
// Use if you want deterministic fuzzing.
func (f *Fuzzer) RandSource(s rand.Source) *Fuzzer {
f.r = rand.New(s)
return f
}
// NilChance sets the probability of creating a nil pointer, map, or slice to
// 'p'. 'p' should be between 0 (no nils) and 1 (all nils), inclusive.
func (f *Fuzzer) NilChance(p float64) *Fuzzer {
if p < 0 || p > 1 {
panic("p should be between 0 and 1, inclusive.")
}
f.nilChance = p
return f
}
// NumElements sets the minimum and maximum number of elements that will be
// added to a non-nil map or slice.
func (f *Fuzzer) NumElements(atLeast, atMost int) *Fuzzer {
if atLeast > atMost {
panic("atLeast must be <= atMost")
}
if atLeast < 0 {
panic("atLeast must be >= 0")
}
f.minElements = atLeast
f.maxElements = atMost
return f
}
func (f *Fuzzer) genElementCount() int {
if f.minElements == f.maxElements {
return f.minElements
}
return f.minElements + f.r.Intn(f.maxElements-f.minElements+1)
}
func (f *Fuzzer) genShouldFill() bool {
return f.r.Float64() >= f.nilChance
}
// MaxDepth sets the maximum number of recursive fuzz calls that will be made
// before stopping. This includes struct members, pointers, and map and slice
// elements.
func (f *Fuzzer) MaxDepth(d int) *Fuzzer {
f.maxDepth = d
return f
}
// Skip fields which match the supplied pattern. Call this multiple times if needed
// This is useful to skip XXX_ fields generated by protobuf
func (f *Fuzzer) SkipFieldsWithPattern(pattern *regexp.Regexp) *Fuzzer {
f.skipFieldPatterns = append(f.skipFieldPatterns, pattern)
return f
}
// Fuzz recursively fills all of obj's fields with something random. First
// this tries to find a custom fuzz function (see Funcs). If there is no
// custom function this tests whether the object implements fuzz.Interface and,
// if so, calls Fuzz on it to fuzz itself. If that fails, this will see if
// there is a default fuzz function provided by this package. If all of that
// fails, this will generate random values for all primitive fields and then
// recurse for all non-primitives.
//
// This is safe for cyclic or tree-like structs, up to a limit. Use the
// MaxDepth method to adjust how deep you need it to recurse.
//
// obj must be a pointer. Only exported (public) fields can be set (thanks,
// golang :/ ) Intended for tests, so will panic on bad input or unimplemented
// fields.
func (f *Fuzzer) Fuzz(obj interface{}) {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
panic("needed ptr!")
}
v = v.Elem()
f.fuzzWithContext(v, 0)
}
// FuzzNoCustom is just like Fuzz, except that any custom fuzz function for
// obj's type will not be called and obj will not be tested for fuzz.Interface
// conformance. This applies only to obj and not other instances of obj's
// type.
// Not safe for cyclic or tree-like structs!
// obj must be a pointer. Only exported (public) fields can be set (thanks, golang :/ )
// Intended for tests, so will panic on bad input or unimplemented fields.
func (f *Fuzzer) FuzzNoCustom(obj interface{}) {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
panic("needed ptr!")
}
v = v.Elem()
f.fuzzWithContext(v, flagNoCustomFuzz)
}
const (
// Do not try to find a custom fuzz function. Does not apply recursively.
flagNoCustomFuzz uint64 = 1 << iota
)
func (f *Fuzzer) fuzzWithContext(v reflect.Value, flags uint64) {
fc := &fuzzerContext{fuzzer: f}
fc.doFuzz(v, flags)
}
// fuzzerContext carries context about a single fuzzing run, which lets Fuzzer
// be thread-safe.
type fuzzerContext struct {
fuzzer *Fuzzer
curDepth int
}
func (fc *fuzzerContext) doFuzz(v reflect.Value, flags uint64) {
if fc.curDepth >= fc.fuzzer.maxDepth {
return
}
fc.curDepth++
defer func() { fc.curDepth-- }()
if !v.CanSet() {
return
}
if flags&flagNoCustomFuzz == 0 {
// Check for both pointer and non-pointer custom functions.
if v.CanAddr() && fc.tryCustom(v.Addr()) {
return
}
if fc.tryCustom(v) {
return
}
}
if fn, ok := fillFuncMap[v.Kind()]; ok {
fn(v, fc.fuzzer.r)
return
}
switch v.Kind() {
case reflect.Map:
if fc.fuzzer.genShouldFill() {
v.Set(reflect.MakeMap(v.Type()))
n := fc.fuzzer.genElementCount()
for i := 0; i < n; i++ {
key := reflect.New(v.Type().Key()).Elem()
fc.doFuzz(key, 0)
val := reflect.New(v.Type().Elem()).Elem()
fc.doFuzz(val, 0)
v.SetMapIndex(key, val)
}
return
}
v.Set(reflect.Zero(v.Type()))
case reflect.Ptr:
if fc.fuzzer.genShouldFill() {
v.Set(reflect.New(v.Type().Elem()))
fc.doFuzz(v.Elem(), 0)
return
}
v.Set(reflect.Zero(v.Type()))
case reflect.Slice:
if fc.fuzzer.genShouldFill() {
n := fc.fuzzer.genElementCount()
v.Set(reflect.MakeSlice(v.Type(), n, n))
for i := 0; i < n; i++ {
fc.doFuzz(v.Index(i), 0)
}
return
}
v.Set(reflect.Zero(v.Type()))
case reflect.Array:
if fc.fuzzer.genShouldFill() {
n := v.Len()
for i := 0; i < n; i++ {
fc.doFuzz(v.Index(i), 0)
}
return
}
v.Set(reflect.Zero(v.Type()))
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
skipField := false
fieldName := v.Type().Field(i).Name
for _, pattern := range fc.fuzzer.skipFieldPatterns {
if pattern.MatchString(fieldName) {
skipField = true
break
}
}
if !skipField {
fc.doFuzz(v.Field(i), 0)
}
}
case reflect.Chan:
fallthrough
case reflect.Func:
fallthrough
case reflect.Interface:
fallthrough
default:
panic(fmt.Sprintf("Can't handle %#v", v.Interface()))
}
}
// tryCustom searches for custom handlers, and returns true iff it finds a match
// and successfully randomizes v.
func (fc *fuzzerContext) tryCustom(v reflect.Value) bool {
// First: see if we have a fuzz function for it.
doCustom, ok := fc.fuzzer.fuzzFuncs[v.Type()]
if !ok {
// Second: see if it can fuzz itself.
if v.CanInterface() {
intf := v.Interface()
if fuzzable, ok := intf.(Interface); ok {
fuzzable.Fuzz(Continue{fc: fc, Rand: fc.fuzzer.r})
return true
}
}
// Finally: see if there is a default fuzz function.
doCustom, ok = fc.fuzzer.defaultFuzzFuncs[v.Type()]
if !ok {
return false
}
}
switch v.Kind() {
case reflect.Ptr:
if v.IsNil() {
if !v.CanSet() {
return false
}
v.Set(reflect.New(v.Type().Elem()))
}
case reflect.Map:
if v.IsNil() {
if !v.CanSet() {
return false
}
v.Set(reflect.MakeMap(v.Type()))
}
default:
return false
}
doCustom.Call([]reflect.Value{v, reflect.ValueOf(Continue{
fc: fc,
Rand: fc.fuzzer.r,
})})
return true
}
// Interface represents an object that knows how to fuzz itself. Any time we
// find a type that implements this interface we will delegate the act of
// fuzzing itself.
type Interface interface {
Fuzz(c Continue)
}
// Continue can be passed to custom fuzzing functions to allow them to use
// the correct source of randomness and to continue fuzzing their members.
type Continue struct {
fc *fuzzerContext
// For convenience, Continue implements rand.Rand via embedding.
// Use this for generating any randomness if you want your fuzzing
// to be repeatable for a given seed.
*rand.Rand
}
// Fuzz continues fuzzing obj. obj must be a pointer.
func (c Continue) Fuzz(obj interface{}) {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
panic("needed ptr!")
}
v = v.Elem()
c.fc.doFuzz(v, 0)
}
// FuzzNoCustom continues fuzzing obj, except that any custom fuzz function for
// obj's type will not be called and obj will not be tested for fuzz.Interface
// conformance. This applies only to obj and not other instances of obj's
// type.
func (c Continue) FuzzNoCustom(obj interface{}) {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
panic("needed ptr!")
}
v = v.Elem()
c.fc.doFuzz(v, flagNoCustomFuzz)
}
// RandString makes a random string up to 20 characters long. The returned string
// may include a variety of (valid) UTF-8 encodings.
func (c Continue) RandString() string {
return randString(c.Rand)
}
// RandUint64 makes random 64 bit numbers.
// Weirdly, rand doesn't have a function that gives you 64 random bits.
func (c Continue) RandUint64() uint64 {
return randUint64(c.Rand)
}
// RandBool returns true or false randomly.
func (c Continue) RandBool() bool {
return randBool(c.Rand)
}
func fuzzInt(v reflect.Value, r *rand.Rand) {
v.SetInt(int64(randUint64(r)))
}
func fuzzUint(v reflect.Value, r *rand.Rand) {
v.SetUint(randUint64(r))
}
func fuzzTime(t *time.Time, c Continue) {
var sec, nsec int64
// Allow for about 1000 years of random time values, which keeps things
// like JSON parsing reasonably happy.
sec = c.Rand.Int63n(1000 * 365 * 24 * 60 * 60)
c.Fuzz(&nsec)
*t = time.Unix(sec, nsec)
}
var fillFuncMap = map[reflect.Kind]func(reflect.Value, *rand.Rand){
reflect.Bool: func(v reflect.Value, r *rand.Rand) {
v.SetBool(randBool(r))
},
reflect.Int: fuzzInt,
reflect.Int8: fuzzInt,
reflect.Int16: fuzzInt,
reflect.Int32: fuzzInt,
reflect.Int64: fuzzInt,
reflect.Uint: fuzzUint,
reflect.Uint8: fuzzUint,
reflect.Uint16: fuzzUint,
reflect.Uint32: fuzzUint,
reflect.Uint64: fuzzUint,
reflect.Uintptr: fuzzUint,
reflect.Float32: func(v reflect.Value, r *rand.Rand) {
v.SetFloat(float64(r.Float32()))
},
reflect.Float64: func(v reflect.Value, r *rand.Rand) {
v.SetFloat(r.Float64())
},
reflect.Complex64: func(v reflect.Value, r *rand.Rand) {
v.SetComplex(complex128(complex(r.Float32(), r.Float32())))
},
reflect.Complex128: func(v reflect.Value, r *rand.Rand) {
v.SetComplex(complex(r.Float64(), r.Float64()))
},
reflect.String: func(v reflect.Value, r *rand.Rand) {
v.SetString(randString(r))
},
reflect.UnsafePointer: func(v reflect.Value, r *rand.Rand) {
panic("unimplemented")
},
}
// randBool returns true or false randomly.
func randBool(r *rand.Rand) bool {
return r.Int31()&(1<<30) == 0
}
type int63nPicker interface {
Int63n(int64) int64
}
// UnicodeRange describes a sequential range of unicode characters.
// Last must be numerically greater than First.
type UnicodeRange struct {
First, Last rune
}
// UnicodeRanges describes an arbitrary number of sequential ranges of unicode characters.
// To be useful, each range must have at least one character (First <= Last) and
// there must be at least one range.
type UnicodeRanges []UnicodeRange
// choose returns a random unicode character from the given range, using the
// given randomness source.
func (ur UnicodeRange) choose(r int63nPicker) rune {
count := int64(ur.Last - ur.First + 1)
return ur.First + rune(r.Int63n(count))
}
// CustomStringFuzzFunc constructs a FuzzFunc which produces random strings.
// Each character is selected from the range ur. If there are no characters
// in the range (cr.Last < cr.First), this will panic.
func (ur UnicodeRange) CustomStringFuzzFunc() func(s *string, c Continue) {
ur.check()
return func(s *string, c Continue) {
*s = ur.randString(c.Rand)
}
}
// check is a function that used to check whether the first of ur(UnicodeRange)
// is greater than the last one.
func (ur UnicodeRange) check() {
if ur.Last < ur.First {
panic("The last encoding must be greater than the first one.")
}
}
// randString of UnicodeRange makes a random string up to 20 characters long.
// Each character is selected form ur(UnicodeRange).
func (ur UnicodeRange) randString(r *rand.Rand) string {
n := r.Intn(20)
sb := strings.Builder{}
sb.Grow(n)
for i := 0; i < n; i++ {
sb.WriteRune(ur.choose(r))
}
return sb.String()
}
// defaultUnicodeRanges sets a default unicode range when user do not set
// CustomStringFuzzFunc() but wants fuzz string.
var defaultUnicodeRanges = UnicodeRanges{
{' ', '~'}, // ASCII characters
{'\u00a0', '\u02af'}, // Multi-byte encoded characters
{'\u4e00', '\u9fff'}, // Common CJK (even longer encodings)
}
// CustomStringFuzzFunc constructs a FuzzFunc which produces random strings.
// Each character is selected from one of the ranges of ur(UnicodeRanges).
// Each range has an equal probability of being chosen. If there are no ranges,
// or a selected range has no characters (.Last < .First), this will panic.
// Do not modify any of the ranges in ur after calling this function.
func (ur UnicodeRanges) CustomStringFuzzFunc() func(s *string, c Continue) {
// Check unicode ranges slice is empty.
if len(ur) == 0 {
panic("UnicodeRanges is empty.")
}
// if not empty, each range should be checked.
for i := range ur {
ur[i].check()
}
return func(s *string, c Continue) {
*s = ur.randString(c.Rand)
}
}
// randString of UnicodeRanges makes a random string up to 20 characters long.
// Each character is selected form one of the ranges of ur(UnicodeRanges),
// and each range has an equal probability of being chosen.
func (ur UnicodeRanges) randString(r *rand.Rand) string {
n := r.Intn(20)
sb := strings.Builder{}
sb.Grow(n)
for i := 0; i < n; i++ {
sb.WriteRune(ur[r.Intn(len(ur))].choose(r))
}
return sb.String()
}
// randString makes a random string up to 20 characters long. The returned string
// may include a variety of (valid) UTF-8 encodings.
func randString(r *rand.Rand) string {
return defaultUnicodeRanges.randString(r)
}
// randUint64 makes random 64 bit numbers.
// Weirdly, rand doesn't have a function that gives you 64 random bits.
func randUint64(r *rand.Rand) uint64 {
return uint64(r.Uint32())<<32 | uint64(r.Uint32())
}

View File

@ -7,19 +7,13 @@ Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol.
---
⚠️ **[The Gorilla WebSocket Package is looking for a new maintainer](https://github.com/gorilla/websocket/issues/370)**
---
### Documentation ### Documentation
* [API Reference](https://pkg.go.dev/github.com/gorilla/websocket?tab=doc) * [API Reference](https://pkg.go.dev/github.com/gorilla/websocket?tab=doc)
* [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat) * [Chat example](https://github.com/gorilla/websocket/tree/main/examples/chat)
* [Command example](https://github.com/gorilla/websocket/tree/master/examples/command) * [Command example](https://github.com/gorilla/websocket/tree/main/examples/command)
* [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo) * [Client and server example](https://github.com/gorilla/websocket/tree/main/examples/echo)
* [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch) * [File watch example](https://github.com/gorilla/websocket/tree/main/examples/filewatch)
### Status ### Status
@ -35,5 +29,4 @@ package API is stable.
The Gorilla WebSocket package passes the server tests in the [Autobahn Test The Gorilla WebSocket package passes the server tests in the [Autobahn Test
Suite](https://github.com/crossbario/autobahn-testsuite) using the application in the [examples/autobahn Suite](https://github.com/crossbario/autobahn-testsuite) using the application in the [examples/autobahn
subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn). subdirectory](https://github.com/gorilla/websocket/tree/main/examples/autobahn).

View File

@ -9,8 +9,8 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt"
"io" "io"
"io/ioutil"
"net" "net"
"net/http" "net/http"
"net/http/httptrace" "net/http/httptrace"
@ -51,18 +51,34 @@ func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufS
// //
// It is safe to call Dialer's methods concurrently. // It is safe to call Dialer's methods concurrently.
type Dialer struct { type Dialer struct {
// The following custom dial functions can be set to establish
// connections to either the backend server or the proxy (if it
// exists). The scheme of the dialed entity (either backend or
// proxy) determines which custom dial function is selected:
// either NetDialTLSContext for HTTPS or NetDialContext/NetDial
// for HTTP. Since the "Proxy" function can determine the scheme
// dynamically, it can make sense to set multiple custom dial
// functions simultaneously.
//
// NetDial specifies the dial function for creating TCP connections. If // NetDial specifies the dial function for creating TCP connections. If
// NetDial is nil, net.Dial is used. // NetDial is nil, net.Dialer DialContext is used.
// If "Proxy" field is also set, this function dials the proxy--not
// the backend server.
NetDial func(network, addr string) (net.Conn, error) NetDial func(network, addr string) (net.Conn, error)
// NetDialContext specifies the dial function for creating TCP connections. If // NetDialContext specifies the dial function for creating TCP connections. If
// NetDialContext is nil, NetDial is used. // NetDialContext is nil, NetDial is used.
// If "Proxy" field is also set, this function dials the proxy--not
// the backend server.
NetDialContext func(ctx context.Context, network, addr string) (net.Conn, error) NetDialContext func(ctx context.Context, network, addr string) (net.Conn, error)
// NetDialTLSContext specifies the dial function for creating TLS/TCP connections. If // NetDialTLSContext specifies the dial function for creating TLS/TCP connections. If
// NetDialTLSContext is nil, NetDialContext is used. // NetDialTLSContext is nil, NetDialContext is used.
// If NetDialTLSContext is set, Dial assumes the TLS handshake is done there and // If NetDialTLSContext is set, Dial assumes the TLS handshake is done there and
// TLSClientConfig is ignored. // TLSClientConfig is ignored.
// If "Proxy" field is also set, this function dials the proxy (and performs
// the TLS handshake with the proxy, ignoring TLSClientConfig). In this TLS proxy
// dialing case the TLSClientConfig could still be necessary for TLS to the backend server.
NetDialTLSContext func(ctx context.Context, network, addr string) (net.Conn, error) NetDialTLSContext func(ctx context.Context, network, addr string) (net.Conn, error)
// Proxy specifies a function to return a proxy for a given // Proxy specifies a function to return a proxy for a given
@ -73,7 +89,7 @@ type Dialer struct {
// TLSClientConfig specifies the TLS configuration to use with tls.Client. // TLSClientConfig specifies the TLS configuration to use with tls.Client.
// If nil, the default configuration is used. // If nil, the default configuration is used.
// If either NetDialTLS or NetDialTLSContext are set, Dial assumes the TLS handshake // If NetDialTLSContext is set, Dial assumes the TLS handshake
// is done there and TLSClientConfig is ignored. // is done there and TLSClientConfig is ignored.
TLSClientConfig *tls.Config TLSClientConfig *tls.Config
@ -244,72 +260,17 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
defer cancel() defer cancel()
} }
// Get network dial function. var proxyURL *url.URL
var netDial func(network, add string) (net.Conn, error)
switch u.Scheme {
case "http":
if d.NetDialContext != nil {
netDial = func(network, addr string) (net.Conn, error) {
return d.NetDialContext(ctx, network, addr)
}
} else if d.NetDial != nil {
netDial = d.NetDial
}
case "https":
if d.NetDialTLSContext != nil {
netDial = func(network, addr string) (net.Conn, error) {
return d.NetDialTLSContext(ctx, network, addr)
}
} else if d.NetDialContext != nil {
netDial = func(network, addr string) (net.Conn, error) {
return d.NetDialContext(ctx, network, addr)
}
} else if d.NetDial != nil {
netDial = d.NetDial
}
default:
return nil, nil, errMalformedURL
}
if netDial == nil {
netDialer := &net.Dialer{}
netDial = func(network, addr string) (net.Conn, error) {
return netDialer.DialContext(ctx, network, addr)
}
}
// If needed, wrap the dial function to set the connection deadline.
if deadline, ok := ctx.Deadline(); ok {
forwardDial := netDial
netDial = func(network, addr string) (net.Conn, error) {
c, err := forwardDial(network, addr)
if err != nil {
return nil, err
}
err = c.SetDeadline(deadline)
if err != nil {
c.Close()
return nil, err
}
return c, nil
}
}
// If needed, wrap the dial function to connect through a proxy.
if d.Proxy != nil { if d.Proxy != nil {
proxyURL, err := d.Proxy(req) proxyURL, err = d.Proxy(req)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
if proxyURL != nil { }
dialer, err := proxy_FromURL(proxyURL, netDialerFunc(netDial)) netDial, err := d.netDialFn(ctx, proxyURL, u)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
netDial = dialer.Dial
}
}
hostPort, hostNoPort := hostPortNoPort(u) hostPort, hostNoPort := hostPortNoPort(u)
trace := httptrace.ContextClientTrace(ctx) trace := httptrace.ContextClientTrace(ctx)
@ -317,24 +278,30 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
trace.GetConn(hostPort) trace.GetConn(hostPort)
} }
netConn, err := netDial("tcp", hostPort) netConn, err := netDial(ctx, "tcp", hostPort)
if err != nil {
return nil, nil, err
}
if trace != nil && trace.GotConn != nil { if trace != nil && trace.GotConn != nil {
trace.GotConn(httptrace.GotConnInfo{ trace.GotConn(httptrace.GotConnInfo{
Conn: netConn, Conn: netConn,
}) })
} }
if err != nil {
return nil, nil, err
}
// Close the network connection when returning an error. The variable
// netConn is set to nil before the success return at the end of the
// function.
defer func() { defer func() {
if netConn != nil { if netConn != nil {
netConn.Close() // It's safe to ignore the error from Close() because this code is
// only executed when returning a more important error to the
// application.
_ = netConn.Close()
} }
}() }()
if u.Scheme == "https" && d.NetDialTLSContext == nil { // Do TLS handshake over established connection if a proxy exists.
// If NetDialTLSContext is set, assume that the TLS handshake has already been done if proxyURL != nil && u.Scheme == "https" {
cfg := cloneTLSConfig(d.TLSClientConfig) cfg := cloneTLSConfig(d.TLSClientConfig)
if cfg.ServerName == "" { if cfg.ServerName == "" {
@ -370,6 +337,17 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
resp, err := http.ReadResponse(conn.br, req) resp, err := http.ReadResponse(conn.br, req)
if err != nil { if err != nil {
if d.TLSClientConfig != nil {
for _, proto := range d.TLSClientConfig.NextProtos {
if proto != "http/1.1" {
return nil, nil, fmt.Errorf(
"websocket: protocol %q was given but is not supported;"+
"sharing tls.Config with net/http Transport can cause this error: %w",
proto, err,
)
}
}
}
return nil, nil, err return nil, nil, err
} }
@ -388,7 +366,7 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
// debugging. // debugging.
buf := make([]byte, 1024) buf := make([]byte, 1024)
n, _ := io.ReadFull(resp.Body, buf) n, _ := io.ReadFull(resp.Body, buf)
resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n])) resp.Body = io.NopCloser(bytes.NewReader(buf[:n]))
return nil, resp, ErrBadHandshake return nil, resp, ErrBadHandshake
} }
@ -406,17 +384,134 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
break break
} }
resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{})) resp.Body = io.NopCloser(bytes.NewReader([]byte{}))
conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol") conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol")
netConn.SetDeadline(time.Time{}) if err := netConn.SetDeadline(time.Time{}); err != nil {
netConn = nil // to avoid close in defer. return nil, resp, err
}
// Success! Set netConn to nil to stop the deferred function above from
// closing the network connection.
netConn = nil
return conn, resp, nil return conn, resp, nil
} }
// Returns the dial function to establish the connection to either the backend
// server or the proxy (if it exists). If the dialed entity is HTTPS, then the
// returned dial function *also* performs the TLS handshake to the dialed entity.
// NOTE: If a proxy exists, it is possible for a second TLS handshake to be
// necessary over the established connection.
func (d *Dialer) netDialFn(ctx context.Context, proxyURL *url.URL, backendURL *url.URL) (netDialerFunc, error) {
var netDial netDialerFunc
if proxyURL != nil {
netDial = d.netDialFromURL(proxyURL)
} else {
netDial = d.netDialFromURL(backendURL)
}
// If needed, wrap the dial function to set the connection deadline.
if deadline, ok := ctx.Deadline(); ok {
netDial = netDialWithDeadline(netDial, deadline)
}
// Proxy dialing is wrapped to implement CONNECT method and possibly proxy auth.
if proxyURL != nil {
return proxyFromURL(proxyURL, netDial)
}
return netDial, nil
}
// Returns function to create the connection depending on the Dialer's
// custom dialing functions and the passed URL of entity connecting to.
func (d *Dialer) netDialFromURL(u *url.URL) netDialerFunc {
var netDial netDialerFunc
switch {
case d.NetDialContext != nil:
netDial = d.NetDialContext
case d.NetDial != nil:
netDial = func(ctx context.Context, net, addr string) (net.Conn, error) {
return d.NetDial(net, addr)
}
default:
netDial = (&net.Dialer{}).DialContext
}
// If dialed entity is HTTPS, then either use custom TLS dialing function (if exists)
// or wrap the previously computed "netDial" to use TLS config for handshake.
if u.Scheme == "https" {
if d.NetDialTLSContext != nil {
netDial = d.NetDialTLSContext
} else {
netDial = netDialWithTLSHandshake(netDial, d.TLSClientConfig, u)
}
}
return netDial
}
// Returns wrapped "netDial" function, performing TLS handshake after connecting.
func netDialWithTLSHandshake(netDial netDialerFunc, tlsConfig *tls.Config, u *url.URL) netDialerFunc {
return func(ctx context.Context, unused, addr string) (net.Conn, error) {
hostPort, hostNoPort := hostPortNoPort(u)
trace := httptrace.ContextClientTrace(ctx)
if trace != nil && trace.GetConn != nil {
trace.GetConn(hostPort)
}
// Creates TCP connection to addr using passed "netDial" function.
conn, err := netDial(ctx, "tcp", addr)
if err != nil {
return nil, err
}
cfg := cloneTLSConfig(tlsConfig)
if cfg.ServerName == "" {
cfg.ServerName = hostNoPort
}
tlsConn := tls.Client(conn, cfg)
// Do the TLS handshake using TLSConfig over the wrapped connection.
if trace != nil && trace.TLSHandshakeStart != nil {
trace.TLSHandshakeStart()
}
err = doHandshake(ctx, tlsConn, cfg)
if trace != nil && trace.TLSHandshakeDone != nil {
trace.TLSHandshakeDone(tlsConn.ConnectionState(), err)
}
if err != nil {
tlsConn.Close()
return nil, err
}
return tlsConn, nil
}
}
// Returns wrapped "netDial" function, setting passed deadline.
func netDialWithDeadline(netDial netDialerFunc, deadline time.Time) netDialerFunc {
return func(ctx context.Context, network, addr string) (net.Conn, error) {
c, err := netDial(ctx, network, addr)
if err != nil {
return nil, err
}
err = c.SetDeadline(deadline)
if err != nil {
c.Close()
return nil, err
}
return c, nil
}
}
func cloneTLSConfig(cfg *tls.Config) *tls.Config { func cloneTLSConfig(cfg *tls.Config) *tls.Config {
if cfg == nil { if cfg == nil {
return &tls.Config{} return &tls.Config{}
} }
return cfg.Clone() return cfg.Clone()
} }
func doHandshake(ctx context.Context, tlsConn *tls.Conn, cfg *tls.Config) error {
if err := tlsConn.HandshakeContext(ctx); err != nil {
return err
}
if !cfg.InsecureSkipVerify {
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
return err
}
}
return nil
}

View File

@ -33,7 +33,11 @@ func decompressNoContextTakeover(r io.Reader) io.ReadCloser {
"\x01\x00\x00\xff\xff" "\x01\x00\x00\xff\xff"
fr, _ := flateReaderPool.Get().(io.ReadCloser) fr, _ := flateReaderPool.Get().(io.ReadCloser)
fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil) mr := io.MultiReader(r, strings.NewReader(tail))
if err := fr.(flate.Resetter).Reset(mr, nil); err != nil {
// Reset never fails, but handle error in case that changes.
fr = flate.NewReader(mr)
}
return &flateReadWrapper{fr} return &flateReadWrapper{fr}
} }

View File

@ -6,11 +6,10 @@ package websocket
import ( import (
"bufio" "bufio"
"crypto/rand"
"encoding/binary" "encoding/binary"
"errors" "errors"
"io" "io"
"io/ioutil"
"math/rand"
"net" "net"
"strconv" "strconv"
"strings" "strings"
@ -181,16 +180,16 @@ var (
errInvalidControlFrame = errors.New("websocket: invalid control frame") errInvalidControlFrame = errors.New("websocket: invalid control frame")
) )
func newMaskKey() [4]byte { // maskRand is an io.Reader for generating mask bytes. The reader is initialized
n := rand.Uint32() // to crypto/rand Reader. Tests swap the reader to a math/rand reader for
return [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)} // reproducible results.
} var maskRand = rand.Reader
func hideTempErr(err error) error { // newMaskKey returns a new 32 bit value for masking client frames.
if e, ok := err.(net.Error); ok && e.Temporary() { func newMaskKey() [4]byte {
err = &netError{msg: e.Error(), timeout: e.Timeout()} var k [4]byte
} _, _ = io.ReadFull(maskRand, k[:])
return err return k
} }
func isControl(frameType int) bool { func isControl(frameType int) bool {
@ -358,7 +357,6 @@ func (c *Conn) RemoteAddr() net.Addr {
// Write methods // Write methods
func (c *Conn) writeFatal(err error) error { func (c *Conn) writeFatal(err error) error {
err = hideTempErr(err)
c.writeErrMu.Lock() c.writeErrMu.Lock()
if c.writeErr == nil { if c.writeErr == nil {
c.writeErr = err c.writeErr = err
@ -372,7 +370,9 @@ func (c *Conn) read(n int) ([]byte, error) {
if err == io.EOF { if err == io.EOF {
err = errUnexpectedEOF err = errUnexpectedEOF
} }
c.br.Discard(len(p)) // Discard is guaranteed to succeed because the number of bytes to discard
// is less than or equal to the number of bytes buffered.
_, _ = c.br.Discard(len(p))
return p, err return p, err
} }
@ -387,7 +387,9 @@ func (c *Conn) write(frameType int, deadline time.Time, buf0, buf1 []byte) error
return err return err
} }
c.conn.SetWriteDeadline(deadline) if err := c.conn.SetWriteDeadline(deadline); err != nil {
return c.writeFatal(err)
}
if len(buf1) == 0 { if len(buf1) == 0 {
_, err = c.conn.Write(buf0) _, err = c.conn.Write(buf0)
} else { } else {
@ -397,7 +399,7 @@ func (c *Conn) write(frameType int, deadline time.Time, buf0, buf1 []byte) error
return c.writeFatal(err) return c.writeFatal(err)
} }
if frameType == CloseMessage { if frameType == CloseMessage {
c.writeFatal(ErrCloseSent) _ = c.writeFatal(ErrCloseSent)
} }
return nil return nil
} }
@ -436,14 +438,17 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er
maskBytes(key, 0, buf[6:]) maskBytes(key, 0, buf[6:])
} }
d := 1000 * time.Hour if deadline.IsZero() {
if !deadline.IsZero() { // No timeout for zero time.
d = deadline.Sub(time.Now()) <-c.mu
} else {
d := time.Until(deadline)
if d < 0 { if d < 0 {
return errWriteTimeout return errWriteTimeout
} }
} select {
case <-c.mu:
default:
timer := time.NewTimer(d) timer := time.NewTimer(d)
select { select {
case <-c.mu: case <-c.mu:
@ -451,6 +456,9 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er
case <-timer.C: case <-timer.C:
return errWriteTimeout return errWriteTimeout
} }
}
}
defer func() { c.mu <- struct{}{} }() defer func() { c.mu <- struct{}{} }()
c.writeErrMu.Lock() c.writeErrMu.Lock()
@ -460,13 +468,14 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er
return err return err
} }
c.conn.SetWriteDeadline(deadline) if err := c.conn.SetWriteDeadline(deadline); err != nil {
_, err = c.conn.Write(buf) return c.writeFatal(err)
if err != nil { }
if _, err = c.conn.Write(buf); err != nil {
return c.writeFatal(err) return c.writeFatal(err)
} }
if messageType == CloseMessage { if messageType == CloseMessage {
c.writeFatal(ErrCloseSent) _ = c.writeFatal(ErrCloseSent)
} }
return err return err
} }
@ -630,7 +639,7 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error {
} }
if final { if final {
w.endMessage(errWriteClosed) _ = w.endMessage(errWriteClosed)
return nil return nil
} }
@ -795,7 +804,7 @@ func (c *Conn) advanceFrame() (int, error) {
// 1. Skip remainder of previous frame. // 1. Skip remainder of previous frame.
if c.readRemaining > 0 { if c.readRemaining > 0 {
if _, err := io.CopyN(ioutil.Discard, c.br, c.readRemaining); err != nil { if _, err := io.CopyN(io.Discard, c.br, c.readRemaining); err != nil {
return noFrame, err return noFrame, err
} }
} }
@ -817,7 +826,7 @@ func (c *Conn) advanceFrame() (int, error) {
rsv2 := p[0]&rsv2Bit != 0 rsv2 := p[0]&rsv2Bit != 0
rsv3 := p[0]&rsv3Bit != 0 rsv3 := p[0]&rsv3Bit != 0
mask := p[1]&maskBit != 0 mask := p[1]&maskBit != 0
c.setReadRemaining(int64(p[1] & 0x7f)) _ = c.setReadRemaining(int64(p[1] & 0x7f)) // will not fail because argument is >= 0
c.readDecompress = false c.readDecompress = false
if rsv1 { if rsv1 {
@ -922,7 +931,8 @@ func (c *Conn) advanceFrame() (int, error) {
} }
if c.readLimit > 0 && c.readLength > c.readLimit { if c.readLimit > 0 && c.readLength > c.readLimit {
c.WriteControl(CloseMessage, FormatCloseMessage(CloseMessageTooBig, ""), time.Now().Add(writeWait)) // Make a best effort to send a close message describing the problem.
_ = c.WriteControl(CloseMessage, FormatCloseMessage(CloseMessageTooBig, ""), time.Now().Add(writeWait))
return noFrame, ErrReadLimit return noFrame, ErrReadLimit
} }
@ -934,7 +944,7 @@ func (c *Conn) advanceFrame() (int, error) {
var payload []byte var payload []byte
if c.readRemaining > 0 { if c.readRemaining > 0 {
payload, err = c.read(int(c.readRemaining)) payload, err = c.read(int(c.readRemaining))
c.setReadRemaining(0) _ = c.setReadRemaining(0) // will not fail because argument is >= 0
if err != nil { if err != nil {
return noFrame, err return noFrame, err
} }
@ -981,7 +991,8 @@ func (c *Conn) handleProtocolError(message string) error {
if len(data) > maxControlFramePayloadSize { if len(data) > maxControlFramePayloadSize {
data = data[:maxControlFramePayloadSize] data = data[:maxControlFramePayloadSize]
} }
c.WriteControl(CloseMessage, data, time.Now().Add(writeWait)) // Make a best effor to send a close message describing the problem.
_ = c.WriteControl(CloseMessage, data, time.Now().Add(writeWait))
return errors.New("websocket: " + message) return errors.New("websocket: " + message)
} }
@ -1008,7 +1019,7 @@ func (c *Conn) NextReader() (messageType int, r io.Reader, err error) {
for c.readErr == nil { for c.readErr == nil {
frameType, err := c.advanceFrame() frameType, err := c.advanceFrame()
if err != nil { if err != nil {
c.readErr = hideTempErr(err) c.readErr = err
break break
} }
@ -1048,13 +1059,13 @@ func (r *messageReader) Read(b []byte) (int, error) {
b = b[:c.readRemaining] b = b[:c.readRemaining]
} }
n, err := c.br.Read(b) n, err := c.br.Read(b)
c.readErr = hideTempErr(err) c.readErr = err
if c.isServer { if c.isServer {
c.readMaskPos = maskBytes(c.readMaskKey, c.readMaskPos, b[:n]) c.readMaskPos = maskBytes(c.readMaskKey, c.readMaskPos, b[:n])
} }
rem := c.readRemaining rem := c.readRemaining
rem -= int64(n) rem -= int64(n)
c.setReadRemaining(rem) _ = c.setReadRemaining(rem) // rem is guaranteed to be >= 0
if c.readRemaining > 0 && c.readErr == io.EOF { if c.readRemaining > 0 && c.readErr == io.EOF {
c.readErr = errUnexpectedEOF c.readErr = errUnexpectedEOF
} }
@ -1069,7 +1080,7 @@ func (r *messageReader) Read(b []byte) (int, error) {
frameType, err := c.advanceFrame() frameType, err := c.advanceFrame()
switch { switch {
case err != nil: case err != nil:
c.readErr = hideTempErr(err) c.readErr = err
case frameType == TextMessage || frameType == BinaryMessage: case frameType == TextMessage || frameType == BinaryMessage:
c.readErr = errors.New("websocket: internal error, unexpected text or binary in Reader") c.readErr = errors.New("websocket: internal error, unexpected text or binary in Reader")
} }
@ -1094,7 +1105,7 @@ func (c *Conn) ReadMessage() (messageType int, p []byte, err error) {
if err != nil { if err != nil {
return messageType, nil, err return messageType, nil, err
} }
p, err = ioutil.ReadAll(r) p, err = io.ReadAll(r)
return messageType, p, err return messageType, p, err
} }
@ -1136,7 +1147,8 @@ func (c *Conn) SetCloseHandler(h func(code int, text string) error) {
if h == nil { if h == nil {
h = func(code int, text string) error { h = func(code int, text string) error {
message := FormatCloseMessage(code, "") message := FormatCloseMessage(code, "")
c.WriteControl(CloseMessage, message, time.Now().Add(writeWait)) // Make a best effor to send the close message.
_ = c.WriteControl(CloseMessage, message, time.Now().Add(writeWait))
return nil return nil
} }
} }
@ -1158,13 +1170,9 @@ func (c *Conn) PingHandler() func(appData string) error {
func (c *Conn) SetPingHandler(h func(appData string) error) { func (c *Conn) SetPingHandler(h func(appData string) error) {
if h == nil { if h == nil {
h = func(message string) error { h = func(message string) error {
err := c.WriteControl(PongMessage, []byte(message), time.Now().Add(writeWait)) // Make a best effort to send the pong message.
if err == ErrCloseSent { _ = c.WriteControl(PongMessage, []byte(message), time.Now().Add(writeWait))
return nil return nil
} else if e, ok := err.(net.Error); ok && e.Temporary() {
return nil
}
return err
} }
} }
c.handlePing = h c.handlePing = h
@ -1189,8 +1197,16 @@ func (c *Conn) SetPongHandler(h func(appData string) error) {
c.handlePong = h c.handlePong = h
} }
// NetConn returns the underlying connection that is wrapped by c.
// Note that writing to or reading from this connection directly will corrupt the
// WebSocket connection.
func (c *Conn) NetConn() net.Conn {
return c.conn
}
// UnderlyingConn returns the internal net.Conn. This can be used to further // UnderlyingConn returns the internal net.Conn. This can be used to further
// modifications to connection specific flags. // modifications to connection specific flags.
// Deprecated: Use the NetConn method.
func (c *Conn) UnderlyingConn() net.Conn { func (c *Conn) UnderlyingConn() net.Conn {
return c.conn return c.conn
} }

View File

@ -6,34 +6,52 @@ package websocket
import ( import (
"bufio" "bufio"
"bytes"
"context"
"encoding/base64" "encoding/base64"
"errors" "errors"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
"golang.org/x/net/proxy"
) )
type netDialerFunc func(network, addr string) (net.Conn, error) type netDialerFunc func(ctx context.Context, network, addr string) (net.Conn, error)
func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) { func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) {
return fn(network, addr) return fn(context.Background(), network, addr)
} }
func init() { func (fn netDialerFunc) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
proxy_RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy_Dialer) (proxy_Dialer, error) { return fn(ctx, network, addr)
return &httpProxyDialer{proxyURL: proxyURL, forwardDial: forwardDialer.Dial}, nil }
})
func proxyFromURL(proxyURL *url.URL, forwardDial netDialerFunc) (netDialerFunc, error) {
if proxyURL.Scheme == "http" || proxyURL.Scheme == "https" {
return (&httpProxyDialer{proxyURL: proxyURL, forwardDial: forwardDial}).DialContext, nil
}
dialer, err := proxy.FromURL(proxyURL, forwardDial)
if err != nil {
return nil, err
}
if d, ok := dialer.(proxy.ContextDialer); ok {
return d.DialContext, nil
}
return func(ctx context.Context, net, addr string) (net.Conn, error) {
return dialer.Dial(net, addr)
}, nil
} }
type httpProxyDialer struct { type httpProxyDialer struct {
proxyURL *url.URL proxyURL *url.URL
forwardDial func(network, addr string) (net.Conn, error) forwardDial netDialerFunc
} }
func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) { func (hpd *httpProxyDialer) DialContext(ctx context.Context, network string, addr string) (net.Conn, error) {
hostPort, _ := hostPortNoPort(hpd.proxyURL) hostPort, _ := hostPortNoPort(hpd.proxyURL)
conn, err := hpd.forwardDial(network, hostPort) conn, err := hpd.forwardDial(ctx, network, hostPort)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -46,7 +64,6 @@ func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error)
connectHeader.Set("Proxy-Authorization", "Basic "+credential) connectHeader.Set("Proxy-Authorization", "Basic "+credential)
} }
} }
connectReq := &http.Request{ connectReq := &http.Request{
Method: http.MethodConnect, Method: http.MethodConnect,
URL: &url.URL{Opaque: addr}, URL: &url.URL{Opaque: addr},
@ -59,7 +76,7 @@ func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error)
return nil, err return nil, err
} }
// Read response. It's OK to use and discard buffered reader here becaue // Read response. It's OK to use and discard buffered reader here because
// the remote server does not speak until spoken to. // the remote server does not speak until spoken to.
br := bufio.NewReader(conn) br := bufio.NewReader(conn)
resp, err := http.ReadResponse(br, connectReq) resp, err := http.ReadResponse(br, connectReq)
@ -68,8 +85,18 @@ func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error)
return nil, err return nil, err
} }
if resp.StatusCode != 200 { // Close the response body to silence false positives from linters. Reset
conn.Close() // the buffered reader first to ensure that Close() does not read from
// conn.
// Note: Applications must call resp.Body.Close() on a response returned
// http.ReadResponse to inspect trailers or read another response from the
// buffered reader. The call to resp.Body.Close() does not release
// resources.
br.Reset(bytes.NewReader(nil))
_ = resp.Body.Close()
if resp.StatusCode != http.StatusOK {
_ = conn.Close()
f := strings.SplitN(resp.Status, " ", 2) f := strings.SplitN(resp.Status, " ", 2)
return nil, errors.New(f[1]) return nil, errors.New(f[1])
} }

View File

@ -6,8 +6,7 @@ package websocket
import ( import (
"bufio" "bufio"
"errors" "net"
"io"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
@ -101,8 +100,8 @@ func checkSameOrigin(r *http.Request) bool {
func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string { func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string {
if u.Subprotocols != nil { if u.Subprotocols != nil {
clientProtocols := Subprotocols(r) clientProtocols := Subprotocols(r)
for _, serverProtocol := range u.Subprotocols {
for _, clientProtocol := range clientProtocols { for _, clientProtocol := range clientProtocols {
for _, serverProtocol := range u.Subprotocols {
if clientProtocol == serverProtocol { if clientProtocol == serverProtocol {
return clientProtocol return clientProtocol
} }
@ -130,7 +129,8 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
} }
if !tokenListContainsValue(r.Header, "Upgrade", "websocket") { if !tokenListContainsValue(r.Header, "Upgrade", "websocket") {
return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'websocket' token not found in 'Upgrade' header") w.Header().Set("Upgrade", "websocket")
return u.returnError(w, r, http.StatusUpgradeRequired, badHandshake+"'websocket' token not found in 'Upgrade' header")
} }
if r.Method != http.MethodGet { if r.Method != http.MethodGet {
@ -154,8 +154,8 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
} }
challengeKey := r.Header.Get("Sec-Websocket-Key") challengeKey := r.Header.Get("Sec-Websocket-Key")
if challengeKey == "" { if !isValidChallengeKey(challengeKey) {
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'Sec-WebSocket-Key' header is missing or blank") return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'Sec-WebSocket-Key' header must be Base64 encoded value of 16-byte in length")
} }
subprotocol := u.selectSubprotocol(r, responseHeader) subprotocol := u.selectSubprotocol(r, responseHeader)
@ -172,28 +172,37 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
} }
} }
h, ok := w.(http.Hijacker) netConn, brw, err := http.NewResponseController(w).Hijack()
if !ok {
return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker")
}
var brw *bufio.ReadWriter
netConn, brw, err := h.Hijack()
if err != nil { if err != nil {
return u.returnError(w, r, http.StatusInternalServerError, err.Error()) return u.returnError(w, r, http.StatusInternalServerError,
"websocket: hijack: "+err.Error())
} }
if brw.Reader.Buffered() > 0 { // Close the network connection when returning an error. The variable
netConn.Close() // netConn is set to nil before the success return at the end of the
return nil, errors.New("websocket: client sent data before handshake is complete") // function.
defer func() {
if netConn != nil {
// It's safe to ignore the error from Close() because this code is
// only executed when returning a more important error to the
// application.
_ = netConn.Close()
} }
}()
var br *bufio.Reader var br *bufio.Reader
if u.ReadBufferSize == 0 && bufioReaderSize(netConn, brw.Reader) > 256 { if u.ReadBufferSize == 0 && brw.Reader.Size() > 256 {
// Reuse hijacked buffered reader as connection reader. // Use hijacked buffered reader as the connection reader.
br = brw.Reader br = brw.Reader
} else if brw.Reader.Buffered() > 0 {
// Wrap the network connection to read buffered data in brw.Reader
// before reading from the network connection. This should be rare
// because a client must not send message data before receiving the
// handshake response.
netConn = &brNetConn{br: brw.Reader, Conn: netConn}
} }
buf := bufioWriterBuffer(netConn, brw.Writer) buf := brw.Writer.AvailableBuffer()
var writeBuf []byte var writeBuf []byte
if u.WriteBufferPool == nil && u.WriteBufferSize == 0 && len(buf) >= maxFrameHeaderSize+256 { if u.WriteBufferPool == nil && u.WriteBufferSize == 0 && len(buf) >= maxFrameHeaderSize+256 {
@ -247,19 +256,29 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
} }
p = append(p, "\r\n"...) p = append(p, "\r\n"...)
// Clear deadlines set by HTTP server.
netConn.SetDeadline(time.Time{})
if u.HandshakeTimeout > 0 { if u.HandshakeTimeout > 0 {
netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout)) if err := netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout)); err != nil {
return nil, err
} }
} else {
// Clear deadlines set by HTTP server.
if err := netConn.SetDeadline(time.Time{}); err != nil {
return nil, err
}
}
if _, err = netConn.Write(p); err != nil { if _, err = netConn.Write(p); err != nil {
netConn.Close()
return nil, err return nil, err
} }
if u.HandshakeTimeout > 0 { if u.HandshakeTimeout > 0 {
netConn.SetWriteDeadline(time.Time{}) if err := netConn.SetWriteDeadline(time.Time{}); err != nil {
return nil, err
} }
}
// Success! Set netConn to nil to stop the deferred function above from
// closing the network connection.
netConn = nil
return c, nil return c, nil
} }
@ -327,39 +346,28 @@ func IsWebSocketUpgrade(r *http.Request) bool {
tokenListContainsValue(r.Header, "Upgrade", "websocket") tokenListContainsValue(r.Header, "Upgrade", "websocket")
} }
// bufioReaderSize size returns the size of a bufio.Reader. type brNetConn struct {
func bufioReaderSize(originalReader io.Reader, br *bufio.Reader) int { br *bufio.Reader
// This code assumes that peek on a reset reader returns net.Conn
// bufio.Reader.buf[:0].
// TODO: Use bufio.Reader.Size() after Go 1.10
br.Reset(originalReader)
if p, err := br.Peek(0); err == nil {
return cap(p)
}
return 0
} }
// writeHook is an io.Writer that records the last slice passed to it vio func (b *brNetConn) Read(p []byte) (n int, err error) {
// io.Writer.Write. if b.br != nil {
type writeHook struct { // Limit read to buferred data.
p []byte if n := b.br.Buffered(); len(p) > n {
p = p[:n]
}
n, err = b.br.Read(p)
if b.br.Buffered() == 0 {
b.br = nil
}
return n, err
}
return b.Conn.Read(p)
} }
func (wh *writeHook) Write(p []byte) (int, error) { // NetConn returns the underlying connection that is wrapped by b.
wh.p = p func (b *brNetConn) NetConn() net.Conn {
return len(p), nil return b.Conn
} }
// bufioWriterBuffer grabs the buffer from a bufio.Writer.
func bufioWriterBuffer(originalWriter io.Writer, bw *bufio.Writer) []byte {
// This code assumes that bufio.Writer.buf[:1] is passed to the
// bufio.Writer's underlying writer.
var wh writeHook
bw.Reset(&wh)
bw.WriteByte(0)
bw.Flush()
bw.Reset(originalWriter)
return wh.p[:cap(wh.p)]
}

View File

@ -1,21 +0,0 @@
//go:build go1.17
// +build go1.17
package websocket
import (
"context"
"crypto/tls"
)
func doHandshake(ctx context.Context, tlsConn *tls.Conn, cfg *tls.Config) error {
if err := tlsConn.HandshakeContext(ctx); err != nil {
return err
}
if !cfg.InsecureSkipVerify {
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
return err
}
}
return nil
}

View File

@ -1,21 +0,0 @@
//go:build !go1.17
// +build !go1.17
package websocket
import (
"context"
"crypto/tls"
)
func doHandshake(ctx context.Context, tlsConn *tls.Conn, cfg *tls.Config) error {
if err := tlsConn.Handshake(); err != nil {
return err
}
if !cfg.InsecureSkipVerify {
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
return err
}
}
return nil
}

View File

@ -281,3 +281,18 @@ headers:
} }
return result return result
} }
// isValidChallengeKey checks if the argument meets RFC6455 specification.
func isValidChallengeKey(s string) bool {
// From RFC6455:
//
// A |Sec-WebSocket-Key| header field with a base64-encoded (see
// Section 4 of [RFC4648]) value that, when decoded, is 16 bytes in
// length.
if s == "" {
return false
}
decoded, err := base64.StdEncoding.DecodeString(s)
return err == nil && len(decoded) == 16
}

View File

@ -1,473 +0,0 @@
// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.
//go:generate bundle -o x_net_proxy.go golang.org/x/net/proxy
// Package proxy provides support for a variety of protocols to proxy network
// data.
//
package websocket
import (
"errors"
"io"
"net"
"net/url"
"os"
"strconv"
"strings"
"sync"
)
type proxy_direct struct{}
// Direct is a direct proxy: one that makes network connections directly.
var proxy_Direct = proxy_direct{}
func (proxy_direct) Dial(network, addr string) (net.Conn, error) {
return net.Dial(network, addr)
}
// A PerHost directs connections to a default Dialer unless the host name
// requested matches one of a number of exceptions.
type proxy_PerHost struct {
def, bypass proxy_Dialer
bypassNetworks []*net.IPNet
bypassIPs []net.IP
bypassZones []string
bypassHosts []string
}
// NewPerHost returns a PerHost Dialer that directs connections to either
// defaultDialer or bypass, depending on whether the connection matches one of
// the configured rules.
func proxy_NewPerHost(defaultDialer, bypass proxy_Dialer) *proxy_PerHost {
return &proxy_PerHost{
def: defaultDialer,
bypass: bypass,
}
}
// Dial connects to the address addr on the given network through either
// defaultDialer or bypass.
func (p *proxy_PerHost) Dial(network, addr string) (c net.Conn, err error) {
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
return p.dialerForRequest(host).Dial(network, addr)
}
func (p *proxy_PerHost) dialerForRequest(host string) proxy_Dialer {
if ip := net.ParseIP(host); ip != nil {
for _, net := range p.bypassNetworks {
if net.Contains(ip) {
return p.bypass
}
}
for _, bypassIP := range p.bypassIPs {
if bypassIP.Equal(ip) {
return p.bypass
}
}
return p.def
}
for _, zone := range p.bypassZones {
if strings.HasSuffix(host, zone) {
return p.bypass
}
if host == zone[1:] {
// For a zone ".example.com", we match "example.com"
// too.
return p.bypass
}
}
for _, bypassHost := range p.bypassHosts {
if bypassHost == host {
return p.bypass
}
}
return p.def
}
// AddFromString parses a string that contains comma-separated values
// specifying hosts that should use the bypass proxy. Each value is either an
// IP address, a CIDR range, a zone (*.example.com) or a host name
// (localhost). A best effort is made to parse the string and errors are
// ignored.
func (p *proxy_PerHost) AddFromString(s string) {
hosts := strings.Split(s, ",")
for _, host := range hosts {
host = strings.TrimSpace(host)
if len(host) == 0 {
continue
}
if strings.Contains(host, "/") {
// We assume that it's a CIDR address like 127.0.0.0/8
if _, net, err := net.ParseCIDR(host); err == nil {
p.AddNetwork(net)
}
continue
}
if ip := net.ParseIP(host); ip != nil {
p.AddIP(ip)
continue
}
if strings.HasPrefix(host, "*.") {
p.AddZone(host[1:])
continue
}
p.AddHost(host)
}
}
// AddIP specifies an IP address that will use the bypass proxy. Note that
// this will only take effect if a literal IP address is dialed. A connection
// to a named host will never match an IP.
func (p *proxy_PerHost) AddIP(ip net.IP) {
p.bypassIPs = append(p.bypassIPs, ip)
}
// AddNetwork specifies an IP range that will use the bypass proxy. Note that
// this will only take effect if a literal IP address is dialed. A connection
// to a named host will never match.
func (p *proxy_PerHost) AddNetwork(net *net.IPNet) {
p.bypassNetworks = append(p.bypassNetworks, net)
}
// AddZone specifies a DNS suffix that will use the bypass proxy. A zone of
// "example.com" matches "example.com" and all of its subdomains.
func (p *proxy_PerHost) AddZone(zone string) {
if strings.HasSuffix(zone, ".") {
zone = zone[:len(zone)-1]
}
if !strings.HasPrefix(zone, ".") {
zone = "." + zone
}
p.bypassZones = append(p.bypassZones, zone)
}
// AddHost specifies a host name that will use the bypass proxy.
func (p *proxy_PerHost) AddHost(host string) {
if strings.HasSuffix(host, ".") {
host = host[:len(host)-1]
}
p.bypassHosts = append(p.bypassHosts, host)
}
// A Dialer is a means to establish a connection.
type proxy_Dialer interface {
// Dial connects to the given address via the proxy.
Dial(network, addr string) (c net.Conn, err error)
}
// Auth contains authentication parameters that specific Dialers may require.
type proxy_Auth struct {
User, Password string
}
// FromEnvironment returns the dialer specified by the proxy related variables in
// the environment.
func proxy_FromEnvironment() proxy_Dialer {
allProxy := proxy_allProxyEnv.Get()
if len(allProxy) == 0 {
return proxy_Direct
}
proxyURL, err := url.Parse(allProxy)
if err != nil {
return proxy_Direct
}
proxy, err := proxy_FromURL(proxyURL, proxy_Direct)
if err != nil {
return proxy_Direct
}
noProxy := proxy_noProxyEnv.Get()
if len(noProxy) == 0 {
return proxy
}
perHost := proxy_NewPerHost(proxy, proxy_Direct)
perHost.AddFromString(noProxy)
return perHost
}
// proxySchemes is a map from URL schemes to a function that creates a Dialer
// from a URL with such a scheme.
var proxy_proxySchemes map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error)
// RegisterDialerType takes a URL scheme and a function to generate Dialers from
// a URL with that scheme and a forwarding Dialer. Registered schemes are used
// by FromURL.
func proxy_RegisterDialerType(scheme string, f func(*url.URL, proxy_Dialer) (proxy_Dialer, error)) {
if proxy_proxySchemes == nil {
proxy_proxySchemes = make(map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error))
}
proxy_proxySchemes[scheme] = f
}
// FromURL returns a Dialer given a URL specification and an underlying
// Dialer for it to make network requests.
func proxy_FromURL(u *url.URL, forward proxy_Dialer) (proxy_Dialer, error) {
var auth *proxy_Auth
if u.User != nil {
auth = new(proxy_Auth)
auth.User = u.User.Username()
if p, ok := u.User.Password(); ok {
auth.Password = p
}
}
switch u.Scheme {
case "socks5":
return proxy_SOCKS5("tcp", u.Host, auth, forward)
}
// If the scheme doesn't match any of the built-in schemes, see if it
// was registered by another package.
if proxy_proxySchemes != nil {
if f, ok := proxy_proxySchemes[u.Scheme]; ok {
return f(u, forward)
}
}
return nil, errors.New("proxy: unknown scheme: " + u.Scheme)
}
var (
proxy_allProxyEnv = &proxy_envOnce{
names: []string{"ALL_PROXY", "all_proxy"},
}
proxy_noProxyEnv = &proxy_envOnce{
names: []string{"NO_PROXY", "no_proxy"},
}
)
// envOnce looks up an environment variable (optionally by multiple
// names) once. It mitigates expensive lookups on some platforms
// (e.g. Windows).
// (Borrowed from net/http/transport.go)
type proxy_envOnce struct {
names []string
once sync.Once
val string
}
func (e *proxy_envOnce) Get() string {
e.once.Do(e.init)
return e.val
}
func (e *proxy_envOnce) init() {
for _, n := range e.names {
e.val = os.Getenv(n)
if e.val != "" {
return
}
}
}
// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address
// with an optional username and password. See RFC 1928 and RFC 1929.
func proxy_SOCKS5(network, addr string, auth *proxy_Auth, forward proxy_Dialer) (proxy_Dialer, error) {
s := &proxy_socks5{
network: network,
addr: addr,
forward: forward,
}
if auth != nil {
s.user = auth.User
s.password = auth.Password
}
return s, nil
}
type proxy_socks5 struct {
user, password string
network, addr string
forward proxy_Dialer
}
const proxy_socks5Version = 5
const (
proxy_socks5AuthNone = 0
proxy_socks5AuthPassword = 2
)
const proxy_socks5Connect = 1
const (
proxy_socks5IP4 = 1
proxy_socks5Domain = 3
proxy_socks5IP6 = 4
)
var proxy_socks5Errors = []string{
"",
"general failure",
"connection forbidden",
"network unreachable",
"host unreachable",
"connection refused",
"TTL expired",
"command not supported",
"address type not supported",
}
// Dial connects to the address addr on the given network via the SOCKS5 proxy.
func (s *proxy_socks5) Dial(network, addr string) (net.Conn, error) {
switch network {
case "tcp", "tcp6", "tcp4":
default:
return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network)
}
conn, err := s.forward.Dial(s.network, s.addr)
if err != nil {
return nil, err
}
if err := s.connect(conn, addr); err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
// connect takes an existing connection to a socks5 proxy server,
// and commands the server to extend that connection to target,
// which must be a canonical address with a host and port.
func (s *proxy_socks5) connect(conn net.Conn, target string) error {
host, portStr, err := net.SplitHostPort(target)
if err != nil {
return err
}
port, err := strconv.Atoi(portStr)
if err != nil {
return errors.New("proxy: failed to parse port number: " + portStr)
}
if port < 1 || port > 0xffff {
return errors.New("proxy: port number out of range: " + portStr)
}
// the size here is just an estimate
buf := make([]byte, 0, 6+len(host))
buf = append(buf, proxy_socks5Version)
if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
buf = append(buf, 2 /* num auth methods */, proxy_socks5AuthNone, proxy_socks5AuthPassword)
} else {
buf = append(buf, 1 /* num auth methods */, proxy_socks5AuthNone)
}
if _, err := conn.Write(buf); err != nil {
return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if buf[0] != 5 {
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
}
if buf[1] == 0xff {
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
}
// See RFC 1929
if buf[1] == proxy_socks5AuthPassword {
buf = buf[:0]
buf = append(buf, 1 /* password protocol version */)
buf = append(buf, uint8(len(s.user)))
buf = append(buf, s.user...)
buf = append(buf, uint8(len(s.password)))
buf = append(buf, s.password...)
if _, err := conn.Write(buf); err != nil {
return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if buf[1] != 0 {
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
}
}
buf = buf[:0]
buf = append(buf, proxy_socks5Version, proxy_socks5Connect, 0 /* reserved */)
if ip := net.ParseIP(host); ip != nil {
if ip4 := ip.To4(); ip4 != nil {
buf = append(buf, proxy_socks5IP4)
ip = ip4
} else {
buf = append(buf, proxy_socks5IP6)
}
buf = append(buf, ip...)
} else {
if len(host) > 255 {
return errors.New("proxy: destination host name too long: " + host)
}
buf = append(buf, proxy_socks5Domain)
buf = append(buf, byte(len(host)))
buf = append(buf, host...)
}
buf = append(buf, byte(port>>8), byte(port))
if _, err := conn.Write(buf); err != nil {
return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if _, err := io.ReadFull(conn, buf[:4]); err != nil {
return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
failure := "unknown error"
if int(buf[1]) < len(proxy_socks5Errors) {
failure = proxy_socks5Errors[buf[1]]
}
if len(failure) > 0 {
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
}
bytesToDiscard := 0
switch buf[3] {
case proxy_socks5IP4:
bytesToDiscard = net.IPv4len
case proxy_socks5IP6:
bytesToDiscard = net.IPv6len
case proxy_socks5Domain:
_, err := io.ReadFull(conn, buf[:1])
if err != nil {
return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
bytesToDiscard = int(buf[0])
default:
return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr)
}
if cap(buf) < bytesToDiscard {
buf = make([]byte, bytesToDiscard)
} else {
buf = buf[:bytesToDiscard]
}
if _, err := io.ReadFull(conn, buf); err != nil {
return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
// Also need to discard the port number
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
return nil
}

View File

@ -73,7 +73,7 @@ go_test(
"@org_golang_google_genproto_googleapis_api//httpbody", "@org_golang_google_genproto_googleapis_api//httpbody",
"@org_golang_google_genproto_googleapis_rpc//errdetails", "@org_golang_google_genproto_googleapis_rpc//errdetails",
"@org_golang_google_genproto_googleapis_rpc//status", "@org_golang_google_genproto_googleapis_rpc//status",
"@org_golang_google_grpc//:go_default_library", "@org_golang_google_grpc//:grpc",
"@org_golang_google_grpc//codes", "@org_golang_google_grpc//codes",
"@org_golang_google_grpc//health/grpc_health_v1", "@org_golang_google_grpc//health/grpc_health_v1",
"@org_golang_google_grpc//metadata", "@org_golang_google_grpc//metadata",

View File

@ -49,6 +49,7 @@ var malformedHTTPHeaders = map[string]struct{}{
type ( type (
rpcMethodKey struct{} rpcMethodKey struct{}
httpPathPatternKey struct{} httpPathPatternKey struct{}
httpPatternKey struct{}
AnnotateContextOption func(ctx context.Context) context.Context AnnotateContextOption func(ctx context.Context) context.Context
) )
@ -404,3 +405,13 @@ func HTTPPathPattern(ctx context.Context) (string, bool) {
func withHTTPPathPattern(ctx context.Context, httpPathPattern string) context.Context { func withHTTPPathPattern(ctx context.Context, httpPathPattern string) context.Context {
return context.WithValue(ctx, httpPathPatternKey{}, httpPathPattern) return context.WithValue(ctx, httpPathPatternKey{}, httpPathPattern)
} }
// HTTPPattern returns the HTTP path pattern struct relating to the HTTP handler, if one exists.
func HTTPPattern(ctx context.Context) (Pattern, bool) {
v, ok := ctx.Value(httpPatternKey{}).(Pattern)
return v, ok
}
func withHTTPPattern(ctx context.Context, httpPattern Pattern) context.Context {
return context.WithValue(ctx, httpPatternKey{}, httpPattern)
}

View File

@ -94,7 +94,7 @@ func Int64(val string) (int64, error) {
} }
// Int64Slice converts 'val' where individual integers are separated by // Int64Slice converts 'val' where individual integers are separated by
// 'sep' into a int64 slice. // 'sep' into an int64 slice.
func Int64Slice(val, sep string) ([]int64, error) { func Int64Slice(val, sep string) ([]int64, error) {
s := strings.Split(val, sep) s := strings.Split(val, sep)
values := make([]int64, len(s)) values := make([]int64, len(s))
@ -118,7 +118,7 @@ func Int32(val string) (int32, error) {
} }
// Int32Slice converts 'val' where individual integers are separated by // Int32Slice converts 'val' where individual integers are separated by
// 'sep' into a int32 slice. // 'sep' into an int32 slice.
func Int32Slice(val, sep string) ([]int32, error) { func Int32Slice(val, sep string) ([]int32, error) {
s := strings.Split(val, sep) s := strings.Split(val, sep)
values := make([]int32, len(s)) values := make([]int32, len(s))
@ -190,7 +190,7 @@ func Bytes(val string) ([]byte, error) {
} }
// BytesSlice converts 'val' where individual bytes sequences, encoded in URL-safe // BytesSlice converts 'val' where individual bytes sequences, encoded in URL-safe
// base64 without padding, are separated by 'sep' into a slice of bytes slices slice. // base64 without padding, are separated by 'sep' into a slice of byte slices.
func BytesSlice(val, sep string) ([][]byte, error) { func BytesSlice(val, sep string) ([][]byte, error) {
s := strings.Split(val, sep) s := strings.Split(val, sep)
values := make([][]byte, len(s)) values := make([][]byte, len(s))

View File

@ -81,6 +81,21 @@ func HTTPError(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.R
mux.errorHandler(ctx, mux, marshaler, w, r, err) mux.errorHandler(ctx, mux, marshaler, w, r, err)
} }
// HTTPStreamError uses the mux-configured stream error handler to notify error to the client without closing the connection.
func HTTPStreamError(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, r *http.Request, err error) {
st := mux.streamErrorHandler(ctx, err)
msg := errorChunk(st)
buf, err := marshaler.Marshal(msg)
if err != nil {
grpclog.Errorf("Failed to marshal an error: %v", err)
return
}
if _, err := w.Write(buf); err != nil {
grpclog.Errorf("Failed to notify error to client: %v", err)
return
}
}
// DefaultHTTPErrorHandler is the default error handler. // DefaultHTTPErrorHandler is the default error handler.
// If "err" is a gRPC Status, the function replies with the status code mapped by HTTPStatusFromCode. // If "err" is a gRPC Status, the function replies with the status code mapped by HTTPStatusFromCode.
// If "err" is a HTTPStatusError, the function replies with the status code provide by that struct. This is // If "err" is a HTTPStatusError, the function replies with the status code provide by that struct. This is
@ -93,6 +108,7 @@ func HTTPError(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.R
func DefaultHTTPErrorHandler(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, r *http.Request, err error) { func DefaultHTTPErrorHandler(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, r *http.Request, err error) {
// return Internal when Marshal failed // return Internal when Marshal failed
const fallback = `{"code": 13, "message": "failed to marshal error message"}` const fallback = `{"code": 13, "message": "failed to marshal error message"}`
const fallbackRewriter = `{"code": 13, "message": "failed to rewrite error message"}`
var customStatus *HTTPStatusError var customStatus *HTTPStatusError
if errors.As(err, &customStatus) { if errors.As(err, &customStatus) {
@ -100,19 +116,28 @@ func DefaultHTTPErrorHandler(ctx context.Context, mux *ServeMux, marshaler Marsh
} }
s := status.Convert(err) s := status.Convert(err)
pb := s.Proto()
w.Header().Del("Trailer") w.Header().Del("Trailer")
w.Header().Del("Transfer-Encoding") w.Header().Del("Transfer-Encoding")
contentType := marshaler.ContentType(pb) respRw, err := mux.forwardResponseRewriter(ctx, s.Proto())
if err != nil {
grpclog.Errorf("Failed to rewrite error message %q: %v", s, err)
w.WriteHeader(http.StatusInternalServerError)
if _, err := io.WriteString(w, fallbackRewriter); err != nil {
grpclog.Errorf("Failed to write response: %v", err)
}
return
}
contentType := marshaler.ContentType(respRw)
w.Header().Set("Content-Type", contentType) w.Header().Set("Content-Type", contentType)
if s.Code() == codes.Unauthenticated { if s.Code() == codes.Unauthenticated {
w.Header().Set("WWW-Authenticate", s.Message()) w.Header().Set("WWW-Authenticate", s.Message())
} }
buf, merr := marshaler.Marshal(pb) buf, merr := marshaler.Marshal(respRw)
if merr != nil { if merr != nil {
grpclog.Errorf("Failed to marshal error message %q: %v", s, merr) grpclog.Errorf("Failed to marshal error message %q: %v", s, merr)
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)

View File

@ -155,7 +155,7 @@ func buildPathsBlindly(name string, in interface{}) []string {
return paths return paths
} }
// fieldMaskPathItem stores a in-progress deconstruction of a path for a fieldmask // fieldMaskPathItem stores an in-progress deconstruction of a path for a fieldmask
type fieldMaskPathItem struct { type fieldMaskPathItem struct {
// the list of prior fields leading up to node connected by dots // the list of prior fields leading up to node connected by dots
path string path string

View File

@ -3,6 +3,7 @@ package runtime
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"io" "io"
"net/http" "net/http"
"net/textproto" "net/textproto"
@ -55,20 +56,33 @@ func ForwardResponseStream(ctx context.Context, mux *ServeMux, marshaler Marshal
return return
} }
respRw, err := mux.forwardResponseRewriter(ctx, resp)
if err != nil {
grpclog.Errorf("Rewrite error: %v", err)
handleForwardResponseStreamError(ctx, wroteHeader, marshaler, w, req, mux, err, delimiter)
return
}
if !wroteHeader { if !wroteHeader {
w.Header().Set("Content-Type", marshaler.ContentType(resp)) var contentType string
if sct, ok := marshaler.(StreamContentType); ok {
contentType = sct.StreamContentType(respRw)
} else {
contentType = marshaler.ContentType(respRw)
}
w.Header().Set("Content-Type", contentType)
} }
var buf []byte var buf []byte
httpBody, isHTTPBody := resp.(*httpbody.HttpBody) httpBody, isHTTPBody := respRw.(*httpbody.HttpBody)
switch { switch {
case resp == nil: case respRw == nil:
buf, err = marshaler.Marshal(errorChunk(status.New(codes.Internal, "empty response"))) buf, err = marshaler.Marshal(errorChunk(status.New(codes.Internal, "empty response")))
case isHTTPBody: case isHTTPBody:
buf = httpBody.GetData() buf = httpBody.GetData()
default: default:
result := map[string]interface{}{"result": resp} result := map[string]interface{}{"result": respRw}
if rb, ok := resp.(responseBody); ok { if rb, ok := respRw.(responseBody); ok {
result["result"] = rb.XXX_ResponseBody() result["result"] = rb.XXX_ResponseBody()
} }
@ -164,12 +178,17 @@ func ForwardResponseMessage(ctx context.Context, mux *ServeMux, marshaler Marsha
HTTPError(ctx, mux, marshaler, w, req, err) HTTPError(ctx, mux, marshaler, w, req, err)
return return
} }
respRw, err := mux.forwardResponseRewriter(ctx, resp)
if err != nil {
grpclog.Errorf("Rewrite error: %v", err)
HTTPError(ctx, mux, marshaler, w, req, err)
return
}
var buf []byte var buf []byte
var err error if rb, ok := respRw.(responseBody); ok {
if rb, ok := resp.(responseBody); ok {
buf, err = marshaler.Marshal(rb.XXX_ResponseBody()) buf, err = marshaler.Marshal(rb.XXX_ResponseBody())
} else { } else {
buf, err = marshaler.Marshal(resp) buf, err = marshaler.Marshal(respRw)
} }
if err != nil { if err != nil {
grpclog.Errorf("Marshal error: %v", err) grpclog.Errorf("Marshal error: %v", err)
@ -181,7 +200,7 @@ func ForwardResponseMessage(ctx context.Context, mux *ServeMux, marshaler Marsha
w.Header().Set("Content-Length", strconv.Itoa(len(buf))) w.Header().Set("Content-Length", strconv.Itoa(len(buf)))
} }
if _, err = w.Write(buf); err != nil { if _, err = w.Write(buf); err != nil && !errors.Is(err, http.ErrBodyNotAllowed) {
grpclog.Errorf("Failed to write response: %v", err) grpclog.Errorf("Failed to write response: %v", err)
} }
@ -201,8 +220,7 @@ func handleForwardResponseOptions(ctx context.Context, w http.ResponseWriter, re
} }
for _, opt := range opts { for _, opt := range opts {
if err := opt(ctx, w, resp); err != nil { if err := opt(ctx, w, resp); err != nil {
grpclog.Errorf("Error handling ForwardResponseOptions: %v", err) return fmt.Errorf("error handling ForwardResponseOptions: %w", err)
return err
} }
} }
return nil return nil

View File

@ -48,3 +48,11 @@ type Delimited interface {
// Delimiter returns the record separator for the stream. // Delimiter returns the record separator for the stream.
Delimiter() []byte Delimiter() []byte
} }
// StreamContentType defines the streaming content type.
type StreamContentType interface {
// StreamContentType returns the content type for a stream. This shares the
// same behaviour as for `Marshaler.ContentType`, but is called, if present,
// in the case of a streamed response.
StreamContentType(v interface{}) string
}

View File

@ -86,8 +86,8 @@ func (m marshalerRegistry) add(mime string, marshaler Marshaler) error {
// It allows for a mapping of case-sensitive Content-Type MIME type string to runtime.Marshaler interfaces. // It allows for a mapping of case-sensitive Content-Type MIME type string to runtime.Marshaler interfaces.
// //
// For example, you could allow the client to specify the use of the runtime.JSONPb marshaler // For example, you could allow the client to specify the use of the runtime.JSONPb marshaler
// with a "application/jsonpb" Content-Type and the use of the runtime.JSONBuiltin marshaler // with an "application/jsonpb" Content-Type and the use of the runtime.JSONBuiltin marshaler
// with a "application/json" Content-Type. // with an "application/json" Content-Type.
// "*" can be used to match any Content-Type. // "*" can be used to match any Content-Type.
// This can be attached to a ServerMux with the marshaler option. // This can be attached to a ServerMux with the marshaler option.
func makeMarshalerMIMERegistry() marshalerRegistry { func makeMarshalerMIMERegistry() marshalerRegistry {

View File

@ -48,12 +48,19 @@ var encodedPathSplitter = regexp.MustCompile("(/|%2F)")
// A HandlerFunc handles a specific pair of path pattern and HTTP method. // A HandlerFunc handles a specific pair of path pattern and HTTP method.
type HandlerFunc func(w http.ResponseWriter, r *http.Request, pathParams map[string]string) type HandlerFunc func(w http.ResponseWriter, r *http.Request, pathParams map[string]string)
// A Middleware handler wraps another HandlerFunc to do some pre- and/or post-processing of the request. This is used as an alternative to gRPC interceptors when using the direct-to-implementation
// registration methods. It is generally recommended to use gRPC client or server interceptors instead
// where possible.
type Middleware func(HandlerFunc) HandlerFunc
// ServeMux is a request multiplexer for grpc-gateway. // ServeMux is a request multiplexer for grpc-gateway.
// It matches http requests to patterns and invokes the corresponding handler. // It matches http requests to patterns and invokes the corresponding handler.
type ServeMux struct { type ServeMux struct {
// handlers maps HTTP method to a list of handlers. // handlers maps HTTP method to a list of handlers.
handlers map[string][]handler handlers map[string][]handler
middlewares []Middleware
forwardResponseOptions []func(context.Context, http.ResponseWriter, proto.Message) error forwardResponseOptions []func(context.Context, http.ResponseWriter, proto.Message) error
forwardResponseRewriter ForwardResponseRewriter
marshalers marshalerRegistry marshalers marshalerRegistry
incomingHeaderMatcher HeaderMatcherFunc incomingHeaderMatcher HeaderMatcherFunc
outgoingHeaderMatcher HeaderMatcherFunc outgoingHeaderMatcher HeaderMatcherFunc
@ -69,6 +76,24 @@ type ServeMux struct {
// ServeMuxOption is an option that can be given to a ServeMux on construction. // ServeMuxOption is an option that can be given to a ServeMux on construction.
type ServeMuxOption func(*ServeMux) type ServeMuxOption func(*ServeMux)
// ForwardResponseRewriter is the signature of a function that is capable of rewriting messages
// before they are forwarded in a unary, stream, or error response.
type ForwardResponseRewriter func(ctx context.Context, response proto.Message) (any, error)
// WithForwardResponseRewriter returns a ServeMuxOption that allows for implementers to insert logic
// that can rewrite the final response before it is forwarded.
//
// The response rewriter function is called during unary message forwarding, stream message
// forwarding and when errors are being forwarded.
//
// NOTE: Using this option will likely make what is generated by `protoc-gen-openapiv2` incorrect.
// Since this option involves making runtime changes to the response shape or type.
func WithForwardResponseRewriter(fwdResponseRewriter ForwardResponseRewriter) ServeMuxOption {
return func(sm *ServeMux) {
sm.forwardResponseRewriter = fwdResponseRewriter
}
}
// WithForwardResponseOption returns a ServeMuxOption representing the forwardResponseOption. // WithForwardResponseOption returns a ServeMuxOption representing the forwardResponseOption.
// //
// forwardResponseOption is an option that will be called on the relevant context.Context, // forwardResponseOption is an option that will be called on the relevant context.Context,
@ -89,6 +114,15 @@ func WithUnescapingMode(mode UnescapingMode) ServeMuxOption {
} }
} }
// WithMiddlewares sets server middleware for all handlers. This is useful as an alternative to gRPC
// interceptors when using the direct-to-implementation registration methods and cannot rely
// on gRPC interceptors. It's recommended to use gRPC interceptors instead if possible.
func WithMiddlewares(middlewares ...Middleware) ServeMuxOption {
return func(serveMux *ServeMux) {
serveMux.middlewares = append(serveMux.middlewares, middlewares...)
}
}
// SetQueryParameterParser sets the query parameter parser, used to populate message from query parameters. // SetQueryParameterParser sets the query parameter parser, used to populate message from query parameters.
// Configuring this will mean the generated OpenAPI output is no longer correct, and it should be // Configuring this will mean the generated OpenAPI output is no longer correct, and it should be
// done with careful consideration. // done with careful consideration.
@ -279,6 +313,7 @@ func NewServeMux(opts ...ServeMuxOption) *ServeMux {
serveMux := &ServeMux{ serveMux := &ServeMux{
handlers: make(map[string][]handler), handlers: make(map[string][]handler),
forwardResponseOptions: make([]func(context.Context, http.ResponseWriter, proto.Message) error, 0), forwardResponseOptions: make([]func(context.Context, http.ResponseWriter, proto.Message) error, 0),
forwardResponseRewriter: func(ctx context.Context, response proto.Message) (any, error) { return response, nil },
marshalers: makeMarshalerMIMERegistry(), marshalers: makeMarshalerMIMERegistry(),
errorHandler: DefaultHTTPErrorHandler, errorHandler: DefaultHTTPErrorHandler,
streamErrorHandler: DefaultStreamErrorHandler, streamErrorHandler: DefaultStreamErrorHandler,
@ -305,6 +340,9 @@ func NewServeMux(opts ...ServeMuxOption) *ServeMux {
// Handle associates "h" to the pair of HTTP method and path pattern. // Handle associates "h" to the pair of HTTP method and path pattern.
func (s *ServeMux) Handle(meth string, pat Pattern, h HandlerFunc) { func (s *ServeMux) Handle(meth string, pat Pattern, h HandlerFunc) {
if len(s.middlewares) > 0 {
h = chainMiddlewares(s.middlewares)(h)
}
s.handlers[meth] = append([]handler{{pat: pat, h: h}}, s.handlers[meth]...) s.handlers[meth] = append([]handler{{pat: pat, h: h}}, s.handlers[meth]...)
} }
@ -405,7 +443,7 @@ func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
continue continue
} }
h.h(w, r, pathParams) s.handleHandler(h, w, r, pathParams)
return return
} }
@ -458,7 +496,7 @@ func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.errorHandler(ctx, s, outboundMarshaler, w, r, sterr) s.errorHandler(ctx, s, outboundMarshaler, w, r, sterr)
return return
} }
h.h(w, r, pathParams) s.handleHandler(h, w, r, pathParams)
return return
} }
_, outboundMarshaler := MarshalerForRequest(s, r) _, outboundMarshaler := MarshalerForRequest(s, r)
@ -484,3 +522,16 @@ type handler struct {
pat Pattern pat Pattern
h HandlerFunc h HandlerFunc
} }
func (s *ServeMux) handleHandler(h handler, w http.ResponseWriter, r *http.Request, pathParams map[string]string) {
h.h(w, r.WithContext(withHTTPPattern(r.Context(), h.pat)), pathParams)
}
func chainMiddlewares(mws []Middleware) Middleware {
return func(next HandlerFunc) HandlerFunc {
for i := len(mws); i > 0; i-- {
next = mws[i-1](next)
}
return next
}
}

View File

@ -40,7 +40,7 @@ func Float32P(val string) (*float32, error) {
} }
// Int64P parses the given string representation of an integer // Int64P parses the given string representation of an integer
// and returns a pointer to a int64 whose value is same as the parsed integer. // and returns a pointer to an int64 whose value is same as the parsed integer.
func Int64P(val string) (*int64, error) { func Int64P(val string) (*int64, error) {
i, err := Int64(val) i, err := Int64(val)
if err != nil { if err != nil {
@ -50,7 +50,7 @@ func Int64P(val string) (*int64, error) {
} }
// Int32P parses the given string representation of an integer // Int32P parses the given string representation of an integer
// and returns a pointer to a int32 whose value is same as the parsed integer. // and returns a pointer to an int32 whose value is same as the parsed integer.
func Int32P(val string) (*int32, error) { func Int32P(val string) (*int32, error) {
i, err := Int32(val) i, err := Int32(val)
if err != nil { if err != nil {

View File

@ -291,7 +291,11 @@ func parseMessage(msgDescriptor protoreflect.MessageDescriptor, value string) (p
if err != nil { if err != nil {
return protoreflect.Value{}, err return protoreflect.Value{}, err
} }
msg = timestamppb.New(t) timestamp := timestamppb.New(t)
if ok := timestamp.IsValid(); !ok {
return protoreflect.Value{}, fmt.Errorf("%s before 0001-01-01", value)
}
msg = timestamp
case "google.protobuf.Duration": case "google.protobuf.Duration":
d, err := time.ParseDuration(value) d, err := time.ParseDuration(value)
if err != nil { if err != nil {

View File

@ -1,6 +1,6 @@
package utilities package utilities
// An OpCode is a opcode of compiled path patterns. // OpCode is an opcode of compiled path patterns.
type OpCode int type OpCode int
// These constants are the valid values of OpCode. // These constants are the valid values of OpCode.

View File

@ -5,7 +5,7 @@ import (
"strings" "strings"
) )
// flagInterface is an cut down interface to `flag` // flagInterface is a cut down interface to `flag`
type flagInterface interface { type flagInterface interface {
Var(value flag.Value, name string, usage string) Var(value flag.Value, name string, usage string)
} }

View File

@ -26,7 +26,7 @@ import (
var ( var (
// MinClusterVersion is the min cluster version this etcd binary is compatible with. // MinClusterVersion is the min cluster version this etcd binary is compatible with.
MinClusterVersion = "3.0.0" MinClusterVersion = "3.0.0"
Version = "3.5.16" Version = "3.5.21"
APIVersion = "unknown" APIVersion = "unknown"
// Git SHA Value will be set during build // Git SHA Value will be set during build

View File

@ -11,13 +11,6 @@
go get go.etcd.io/etcd/client/v3 go get go.etcd.io/etcd/client/v3
``` ```
Warning: As etcd 3.5.0 was not yet released, the command above does not work.
After first pre-release of 3.5.0 [#12498](https://github.com/etcd-io/etcd/issues/12498),
etcd can be referenced using:
```
go get go.etcd.io/etcd/client/v3@v3.5.0-pre
```
## Get started ## Get started
Create client using `clientv3.New`: Create client using `clientv3.New`:

View File

@ -263,6 +263,12 @@ func (l *lessor) Leases(ctx context.Context) (*LeaseLeasesResponse, error) {
return nil, ContextError(ctx, err) return nil, ContextError(ctx, err)
} }
// To identify the context passed to `KeepAlive`, a key/value pair is
// attached to the context. The key is a `keepAliveCtxKey` object, and
// the value is the pointer to the context object itself, ensuring
// uniqueness as each context has a unique memory address.
type keepAliveCtxKey struct{}
func (l *lessor) KeepAlive(ctx context.Context, id LeaseID) (<-chan *LeaseKeepAliveResponse, error) { func (l *lessor) KeepAlive(ctx context.Context, id LeaseID) (<-chan *LeaseKeepAliveResponse, error) {
ch := make(chan *LeaseKeepAliveResponse, LeaseResponseChSize) ch := make(chan *LeaseKeepAliveResponse, LeaseResponseChSize)
@ -277,6 +283,10 @@ func (l *lessor) KeepAlive(ctx context.Context, id LeaseID) (<-chan *LeaseKeepAl
default: default:
} }
ka, ok := l.keepAlives[id] ka, ok := l.keepAlives[id]
if ctx.Done() != nil {
ctx = context.WithValue(ctx, keepAliveCtxKey{}, &ctx)
}
if !ok { if !ok {
// create fresh keep alive // create fresh keep alive
ka = &keepAlive{ ka = &keepAlive{
@ -347,7 +357,7 @@ func (l *lessor) keepAliveCtxCloser(ctx context.Context, id LeaseID, donec <-cha
// close channel and remove context if still associated with keep alive // close channel and remove context if still associated with keep alive
for i, c := range ka.ctxs { for i, c := range ka.ctxs {
if c == ctx { if c.Value(keepAliveCtxKey{}) == ctx.Value(keepAliveCtxKey{}) {
close(ka.chs[i]) close(ka.chs[i])
ka.ctxs = append(ka.ctxs[:i], ka.ctxs[i+1:]...) ka.ctxs = append(ka.ctxs[:i], ka.ctxs[i+1:]...)
ka.chs = append(ka.chs[:i], ka.chs[i+1:]...) ka.chs = append(ka.chs[:i], ka.chs[i+1:]...)

View File

@ -18,7 +18,7 @@ var DefaultClient = &http.Client{Transport: NewTransport(http.DefaultTransport)}
// Get is a convenient replacement for http.Get that adds a span around the request. // Get is a convenient replacement for http.Get that adds a span around the request.
func Get(ctx context.Context, targetURL string) (resp *http.Response, err error) { func Get(ctx context.Context, targetURL string) (resp *http.Response, err error) {
req, err := http.NewRequestWithContext(ctx, "GET", targetURL, nil) req, err := http.NewRequestWithContext(ctx, http.MethodGet, targetURL, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -27,7 +27,7 @@ func Get(ctx context.Context, targetURL string) (resp *http.Response, err error)
// Head is a convenient replacement for http.Head that adds a span around the request. // Head is a convenient replacement for http.Head that adds a span around the request.
func Head(ctx context.Context, targetURL string) (resp *http.Response, err error) { func Head(ctx context.Context, targetURL string) (resp *http.Response, err error) {
req, err := http.NewRequestWithContext(ctx, "HEAD", targetURL, nil) req, err := http.NewRequestWithContext(ctx, http.MethodHead, targetURL, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -36,7 +36,7 @@ func Head(ctx context.Context, targetURL string) (resp *http.Response, err error
// Post is a convenient replacement for http.Post that adds a span around the request. // Post is a convenient replacement for http.Post that adds a span around the request.
func Post(ctx context.Context, targetURL, contentType string, body io.Reader) (resp *http.Response, err error) { func Post(ctx context.Context, targetURL, contentType string, body io.Reader) (resp *http.Response, err error) {
req, err := http.NewRequestWithContext(ctx, "POST", targetURL, body) req, err := http.NewRequestWithContext(ctx, http.MethodPost, targetURL, body)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -18,20 +18,6 @@ const (
WriteErrorKey = attribute.Key("http.write_error") // if an error occurred while writing a reply, the string of the error (io.EOF is not recorded) WriteErrorKey = attribute.Key("http.write_error") // if an error occurred while writing a reply, the string of the error (io.EOF is not recorded)
) )
// Server HTTP metrics.
const (
serverRequestSize = "http.server.request.size" // Incoming request bytes total
serverResponseSize = "http.server.response.size" // Incoming response bytes total
serverDuration = "http.server.duration" // Incoming end to end duration, milliseconds
)
// Client HTTP metrics.
const (
clientRequestSize = "http.client.request.size" // Outgoing request bytes total
clientResponseSize = "http.client.response.size" // Outgoing response bytes total
clientDuration = "http.client.duration" // Outgoing end to end duration, milliseconds
)
// Filter is a predicate used to determine whether a given http.request should // Filter is a predicate used to determine whether a given http.request should
// be traced. A Filter must return true if the request should be traced. // be traced. A Filter must return true if the request should be traced.
type Filter func(*http.Request) bool type Filter func(*http.Request) bool

View File

@ -8,6 +8,8 @@ import (
"net/http" "net/http"
"net/http/httptrace" "net/http/httptrace"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/propagation"
@ -35,6 +37,7 @@ type config struct {
TracerProvider trace.TracerProvider TracerProvider trace.TracerProvider
MeterProvider metric.MeterProvider MeterProvider metric.MeterProvider
MetricAttributesFn func(*http.Request) []attribute.KeyValue
} }
// Option interface used for setting optional config properties. // Option interface used for setting optional config properties.
@ -194,3 +197,11 @@ func WithServerName(server string) Option {
c.ServerName = server c.ServerName = server
}) })
} }
// WithMetricAttributesFn returns an Option to set a function that maps an HTTP request to a slice of attribute.KeyValue.
// These attributes will be included in metrics for every request.
func WithMetricAttributesFn(metricAttributesFn func(r *http.Request) []attribute.KeyValue) Option {
return optionFunc(func(c *config) {
c.MetricAttributesFn = metricAttributesFn
})
}

Some files were not shown because too many files have changed in this diff Show More