diff --git a/cmd/external-secrets-operator/go.mod b/cmd/external-secrets-operator/go.mod index 2cb2e2104..4a5c6a36b 100644 --- a/cmd/external-secrets-operator/go.mod +++ b/cmd/external-secrets-operator/go.mod @@ -6,7 +6,7 @@ require ( github.com/cert-manager/cert-manager v1.18.5 github.com/openshift/external-secrets-operator v0.0.0-00010101000000-000000000000 k8s.io/api v0.35.6 - k8s.io/apiextensions-apiserver v0.35.0 + k8s.io/apiextensions-apiserver v0.35.3 k8s.io/apimachinery v0.35.6 k8s.io/client-go v0.35.6 k8s.io/klog/v2 v2.140.0 @@ -29,30 +29,28 @@ require ( github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.22.0 // indirect - github.com/go-openapi/jsonreference v0.21.1 // indirect - github.com/go-openapi/swag v0.24.1 // indirect - github.com/go-openapi/swag/cmdutils v0.24.0 // indirect - github.com/go-openapi/swag/conv v0.24.0 // indirect - github.com/go-openapi/swag/fileutils v0.24.0 // indirect - github.com/go-openapi/swag/jsonname v0.24.0 // indirect - github.com/go-openapi/swag/jsonutils v0.24.0 // indirect - github.com/go-openapi/swag/loading v0.24.0 // indirect - github.com/go-openapi/swag/mangling v0.24.0 // indirect - github.com/go-openapi/swag/netutils v0.24.0 // indirect - github.com/go-openapi/swag/stringutils v0.24.0 // indirect - github.com/go-openapi/swag/typeutils v0.24.0 // indirect - github.com/go-openapi/swag/yamlutils v0.24.0 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/cel-go v0.26.0 // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/cel-go v0.31.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.9.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -65,7 +63,6 @@ require ( github.com/robfig/cron/v3 v3.0.1 // indirect github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.10 // indirect - github.com/stoewer/go-strcase v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect @@ -80,8 +77,7 @@ require ( go.uber.org/zap v1.27.1 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect - golang.org/x/mod v0.32.0 // indirect + golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect golang.org/x/net v0.49.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sync v0.19.0 // indirect @@ -90,13 +86,12 @@ require ( golang.org/x/text v0.33.0 // indirect golang.org/x/time v0.14.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260202165425-ce8ad4cf556b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260202165425-ce8ad4cf556b // indirect google.golang.org/grpc v1.79.3 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiserver v0.35.6 // indirect k8s.io/component-base v0.35.6 // indirect k8s.io/component-helpers v0.35.6 // indirect diff --git a/cmd/external-secrets-operator/go.sum b/cmd/external-secrets-operator/go.sum index 6c641f910..7b5225f07 100644 --- a/cmd/external-secrets-operator/go.sum +++ b/cmd/external-secrets-operator/go.sum @@ -40,44 +40,50 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.22.0 h1:TmMhghgNef9YXxTu1tOopo+0BGEytxA+okbry0HjZsM= -github.com/go-openapi/jsonpointer v0.22.0/go.mod h1:xt3jV88UtExdIkkL7NloURjRQjbeUgcxFblMjq2iaiU= -github.com/go-openapi/jsonreference v0.21.1 h1:bSKrcl8819zKiOgxkbVNRUBIr6Wwj9KYrDbMjRs0cDA= -github.com/go-openapi/jsonreference v0.21.1/go.mod h1:PWs8rO4xxTUqKGu+lEvvCxD5k2X7QYkKAepJyCmSTT8= -github.com/go-openapi/swag v0.24.1 h1:DPdYTZKo6AQCRqzwr/kGkxJzHhpKxZ9i/oX0zag+MF8= -github.com/go-openapi/swag v0.24.1/go.mod h1:sm8I3lCPlspsBBwUm1t5oZeWZS0s7m/A+Psg0ooRU0A= -github.com/go-openapi/swag/cmdutils v0.24.0 h1:KlRCffHwXFI6E5MV9n8o8zBRElpY4uK4yWyAMWETo9I= -github.com/go-openapi/swag/cmdutils v0.24.0/go.mod h1:uxib2FAeQMByyHomTlsP8h1TtPd54Msu2ZDU/H5Vuf8= -github.com/go-openapi/swag/conv v0.24.0 h1:ejB9+7yogkWly6pnruRX45D1/6J+ZxRu92YFivx54ik= -github.com/go-openapi/swag/conv v0.24.0/go.mod h1:jbn140mZd7EW2g8a8Y5bwm8/Wy1slLySQQ0ND6DPc2c= -github.com/go-openapi/swag/fileutils v0.24.0 h1:U9pCpqp4RUytnD689Ek/N1d2N/a//XCeqoH508H5oak= -github.com/go-openapi/swag/fileutils v0.24.0/go.mod h1:3SCrCSBHyP1/N+3oErQ1gP+OX1GV2QYFSnrTbzwli90= -github.com/go-openapi/swag/jsonname v0.24.0 h1:2wKS9bgRV/xB8c62Qg16w4AUiIrqqiniJFtZGi3dg5k= -github.com/go-openapi/swag/jsonname v0.24.0/go.mod h1:GXqrPzGJe611P7LG4QB9JKPtUZ7flE4DOVechNaDd7Q= -github.com/go-openapi/swag/jsonutils v0.24.0 h1:F1vE1q4pg1xtO3HTyJYRmEuJ4jmIp2iZ30bzW5XgZts= -github.com/go-openapi/swag/jsonutils v0.24.0/go.mod h1:vBowZtF5Z4DDApIoxcIVfR8v0l9oq5PpYRUuteVu6f0= -github.com/go-openapi/swag/loading v0.24.0 h1:ln/fWTwJp2Zkj5DdaX4JPiddFC5CHQpvaBKycOlceYc= -github.com/go-openapi/swag/loading v0.24.0/go.mod h1:gShCN4woKZYIxPxbfbyHgjXAhO61m88tmjy0lp/LkJk= -github.com/go-openapi/swag/mangling v0.24.0 h1:PGOQpViCOUroIeak/Uj/sjGAq9LADS3mOyjznmHy2pk= -github.com/go-openapi/swag/mangling v0.24.0/go.mod h1:Jm5Go9LHkycsz0wfoaBDkdc4CkpuSnIEf62brzyCbhc= -github.com/go-openapi/swag/netutils v0.24.0 h1:Bz02HRjYv8046Ycg/w80q3g9QCWeIqTvlyOjQPDjD8w= -github.com/go-openapi/swag/netutils v0.24.0/go.mod h1:WRgiHcYTnx+IqfMCtu0hy9oOaPR0HnPbmArSRN1SkZM= -github.com/go-openapi/swag/stringutils v0.24.0 h1:i4Z/Jawf9EvXOLUbT97O0HbPUja18VdBxeadyAqS1FM= -github.com/go-openapi/swag/stringutils v0.24.0/go.mod h1:5nUXB4xA0kw2df5PRipZDslPJgJut+NjL7D25zPZ/4w= -github.com/go-openapi/swag/typeutils v0.24.0 h1:d3szEGzGDf4L2y1gYOSSLeK6h46F+zibnEas2Jm/wIw= -github.com/go-openapi/swag/typeutils v0.24.0/go.mod h1:q8C3Kmk/vh2VhpCLaoR2MVWOGP8y7Jc8l82qCTd1DYI= -github.com/go-openapi/swag/yamlutils v0.24.0 h1:bhw4894A7Iw6ne+639hsBNRHg9iZg/ISrOVr+sJGp4c= -github.com/go-openapi/swag/yamlutils v0.24.0/go.mod h1:DpKv5aYuaGm/sULePoeiG8uwMpZSfReo1HR3Ik0yaG8= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 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/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI= -github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/cel-go v0.31.0 h1:H0bhpFTqOvmHrBGrWKp7ZlhBm5Hh8PYUEXnwxT1LL7A= +github.com/google/cel-go v0.31.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -91,8 +97,6 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZ github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= @@ -103,8 +107,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -142,17 +144,10 @@ github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiT github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= -github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= @@ -187,8 +182,8 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0= -golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= @@ -211,10 +206,10 @@ gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0 gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/api v0.0.0-20260202165425-ce8ad4cf556b h1:SGYyueaEovpqmWmtTvwtVgo638V/QFE2zlTCnRrR3jg= +google.golang.org/genproto/googleapis/api v0.0.0-20260202165425-ce8ad4cf556b/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260202165425-ce8ad4cf556b h1:GZxXGdFaHX27ZSMHudWc4FokdD+xl8BC2UJm1OVIEzs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260202165425-ce8ad4cf556b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= @@ -226,13 +221,12 @@ gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnf gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.35.6 h1:phPzP79F3kcONsD2TzmDiITNCV6/1Z5U3CCEcjtsXzI= k8s.io/api v0.35.6/go.mod h1:GWKUaIp24fuDFigAgnhr9EJOKDqspnwPjYlpDca5B4U= -k8s.io/apiextensions-apiserver v0.35.0 h1:3xHk2rTOdWXXJM+RDQZJvdx0yEOgC0FgQ1PlJatA5T4= -k8s.io/apiextensions-apiserver v0.35.0/go.mod h1:E1Ahk9SADaLQ4qtzYFkwUqusXTcaV2uw3l14aqpL2LU= +k8s.io/apiextensions-apiserver v0.35.3 h1:2fQUhEO7P17sijylbdwt0nBdXP0TvHrHj0KeqHD8FiU= +k8s.io/apiextensions-apiserver v0.35.3/go.mod h1:tK4Kz58ykRpwAEkXUb634HD1ZAegEElktz/B3jgETd8= k8s.io/apimachinery v0.35.6 h1:ASSpfmmsOArKb2Hsu8gGlIcbIcEMVTboI3FfsfYuQ8k= k8s.io/apimachinery v0.35.6/go.mod h1:NNi1taPOpep0jOj+oRha3mBJPqvi0hGdaV8TCqGQ+cc= k8s.io/apiserver v0.35.6 h1:VWYg2S0wlAmN3URFpVeuLa4PP2RCpTFg1nvlUHOy2C8= diff --git a/go.mod b/go.mod index 846e9c57f..e16251602 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/go-logr/logr v1.4.3 go.uber.org/zap v1.27.1 k8s.io/api v0.35.6 - k8s.io/apiextensions-apiserver v0.35.0 + k8s.io/apiextensions-apiserver v0.35.3 k8s.io/apimachinery v0.35.6 k8s.io/client-go v0.35.6 k8s.io/kubernetes v1.35.6 @@ -25,28 +25,26 @@ require ( github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect - github.com/go-openapi/jsonpointer v0.22.0 // indirect - github.com/go-openapi/jsonreference v0.21.1 // indirect - github.com/go-openapi/swag v0.24.1 // indirect - github.com/go-openapi/swag/cmdutils v0.24.0 // indirect - github.com/go-openapi/swag/conv v0.24.0 // indirect - github.com/go-openapi/swag/fileutils v0.24.0 // indirect - github.com/go-openapi/swag/jsonname v0.24.0 // indirect - github.com/go-openapi/swag/jsonutils v0.24.0 // indirect - github.com/go-openapi/swag/loading v0.24.0 // indirect - github.com/go-openapi/swag/mangling v0.24.0 // indirect - github.com/go-openapi/swag/netutils v0.24.0 // indirect - github.com/go-openapi/swag/stringutils v0.24.0 // indirect - github.com/go-openapi/swag/typeutils v0.24.0 // indirect - github.com/go-openapi/swag/yamlutils v0.24.0 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.9.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -78,7 +76,6 @@ require ( google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiserver v0.35.6 // indirect k8s.io/component-base v0.35.6 // indirect k8s.io/component-helpers v0.35.6 // indirect diff --git a/go.sum b/go.sum index 96f9478c0..c485b8376 100644 --- a/go.sum +++ b/go.sum @@ -28,40 +28,46 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.22.0 h1:TmMhghgNef9YXxTu1tOopo+0BGEytxA+okbry0HjZsM= -github.com/go-openapi/jsonpointer v0.22.0/go.mod h1:xt3jV88UtExdIkkL7NloURjRQjbeUgcxFblMjq2iaiU= -github.com/go-openapi/jsonreference v0.21.1 h1:bSKrcl8819zKiOgxkbVNRUBIr6Wwj9KYrDbMjRs0cDA= -github.com/go-openapi/jsonreference v0.21.1/go.mod h1:PWs8rO4xxTUqKGu+lEvvCxD5k2X7QYkKAepJyCmSTT8= -github.com/go-openapi/swag v0.24.1 h1:DPdYTZKo6AQCRqzwr/kGkxJzHhpKxZ9i/oX0zag+MF8= -github.com/go-openapi/swag v0.24.1/go.mod h1:sm8I3lCPlspsBBwUm1t5oZeWZS0s7m/A+Psg0ooRU0A= -github.com/go-openapi/swag/cmdutils v0.24.0 h1:KlRCffHwXFI6E5MV9n8o8zBRElpY4uK4yWyAMWETo9I= -github.com/go-openapi/swag/cmdutils v0.24.0/go.mod h1:uxib2FAeQMByyHomTlsP8h1TtPd54Msu2ZDU/H5Vuf8= -github.com/go-openapi/swag/conv v0.24.0 h1:ejB9+7yogkWly6pnruRX45D1/6J+ZxRu92YFivx54ik= -github.com/go-openapi/swag/conv v0.24.0/go.mod h1:jbn140mZd7EW2g8a8Y5bwm8/Wy1slLySQQ0ND6DPc2c= -github.com/go-openapi/swag/fileutils v0.24.0 h1:U9pCpqp4RUytnD689Ek/N1d2N/a//XCeqoH508H5oak= -github.com/go-openapi/swag/fileutils v0.24.0/go.mod h1:3SCrCSBHyP1/N+3oErQ1gP+OX1GV2QYFSnrTbzwli90= -github.com/go-openapi/swag/jsonname v0.24.0 h1:2wKS9bgRV/xB8c62Qg16w4AUiIrqqiniJFtZGi3dg5k= -github.com/go-openapi/swag/jsonname v0.24.0/go.mod h1:GXqrPzGJe611P7LG4QB9JKPtUZ7flE4DOVechNaDd7Q= -github.com/go-openapi/swag/jsonutils v0.24.0 h1:F1vE1q4pg1xtO3HTyJYRmEuJ4jmIp2iZ30bzW5XgZts= -github.com/go-openapi/swag/jsonutils v0.24.0/go.mod h1:vBowZtF5Z4DDApIoxcIVfR8v0l9oq5PpYRUuteVu6f0= -github.com/go-openapi/swag/loading v0.24.0 h1:ln/fWTwJp2Zkj5DdaX4JPiddFC5CHQpvaBKycOlceYc= -github.com/go-openapi/swag/loading v0.24.0/go.mod h1:gShCN4woKZYIxPxbfbyHgjXAhO61m88tmjy0lp/LkJk= -github.com/go-openapi/swag/mangling v0.24.0 h1:PGOQpViCOUroIeak/Uj/sjGAq9LADS3mOyjznmHy2pk= -github.com/go-openapi/swag/mangling v0.24.0/go.mod h1:Jm5Go9LHkycsz0wfoaBDkdc4CkpuSnIEf62brzyCbhc= -github.com/go-openapi/swag/netutils v0.24.0 h1:Bz02HRjYv8046Ycg/w80q3g9QCWeIqTvlyOjQPDjD8w= -github.com/go-openapi/swag/netutils v0.24.0/go.mod h1:WRgiHcYTnx+IqfMCtu0hy9oOaPR0HnPbmArSRN1SkZM= -github.com/go-openapi/swag/stringutils v0.24.0 h1:i4Z/Jawf9EvXOLUbT97O0HbPUja18VdBxeadyAqS1FM= -github.com/go-openapi/swag/stringutils v0.24.0/go.mod h1:5nUXB4xA0kw2df5PRipZDslPJgJut+NjL7D25zPZ/4w= -github.com/go-openapi/swag/typeutils v0.24.0 h1:d3szEGzGDf4L2y1gYOSSLeK6h46F+zibnEas2Jm/wIw= -github.com/go-openapi/swag/typeutils v0.24.0/go.mod h1:q8C3Kmk/vh2VhpCLaoR2MVWOGP8y7Jc8l82qCTd1DYI= -github.com/go-openapi/swag/yamlutils v0.24.0 h1:bhw4894A7Iw6ne+639hsBNRHg9iZg/ISrOVr+sJGp4c= -github.com/go-openapi/swag/yamlutils v0.24.0/go.mod h1:DpKv5aYuaGm/sULePoeiG8uwMpZSfReo1HR3Ik0yaG8= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 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/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -71,8 +77,6 @@ github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/v github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= 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/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= @@ -83,8 +87,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -173,8 +175,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.35.6 h1:phPzP79F3kcONsD2TzmDiITNCV6/1Z5U3CCEcjtsXzI= k8s.io/api v0.35.6/go.mod h1:GWKUaIp24fuDFigAgnhr9EJOKDqspnwPjYlpDca5B4U= -k8s.io/apiextensions-apiserver v0.35.0 h1:3xHk2rTOdWXXJM+RDQZJvdx0yEOgC0FgQ1PlJatA5T4= -k8s.io/apiextensions-apiserver v0.35.0/go.mod h1:E1Ahk9SADaLQ4qtzYFkwUqusXTcaV2uw3l14aqpL2LU= +k8s.io/apiextensions-apiserver v0.35.3 h1:2fQUhEO7P17sijylbdwt0nBdXP0TvHrHj0KeqHD8FiU= +k8s.io/apiextensions-apiserver v0.35.3/go.mod h1:tK4Kz58ykRpwAEkXUb634HD1ZAegEElktz/B3jgETd8= k8s.io/apimachinery v0.35.6 h1:ASSpfmmsOArKb2Hsu8gGlIcbIcEMVTboI3FfsfYuQ8k= k8s.io/apimachinery v0.35.6/go.mod h1:NNi1taPOpep0jOj+oRha3mBJPqvi0hGdaV8TCqGQ+cc= k8s.io/apiserver v0.35.6 h1:VWYg2S0wlAmN3URFpVeuLa4PP2RCpTFg1nvlUHOy2C8= diff --git a/go.work b/go.work index bf02f0e78..9a05fcc47 100644 --- a/go.work +++ b/go.work @@ -1,4 +1,4 @@ -go 1.26.0 +go 1.26.3 use ( . diff --git a/go.work.sum b/go.work.sum index 3a9156c8f..a9cafe254 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,7 +1,5 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y= -github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 h1:pWEwq4Asjm4vjW7vcsmijwBhOr1/shsbSYiWXmNGlks= k8s.io/klog v0.2.0 h1:0ElL0OHzF3N+OhoJTL0uca20SxtYt4X4+bzHeqrB83c= diff --git a/pkg/controller/external_secrets/constants.go b/pkg/controller/external_secrets/constants.go index 10ad9742b..b82fa01dc 100644 --- a/pkg/controller/external_secrets/constants.go +++ b/pkg/controller/external_secrets/constants.go @@ -26,6 +26,8 @@ const ( OperandWebhookDeployment = externalsecretsCommonName + "-webhook" // OperandCertControllerDeployment is the in-tree cert-controller Deployment name. OperandCertControllerDeployment = externalsecretsCommonName + "-cert-controller" + // OperandBitwardenSDKServerDeployment is the bitwarden-sdk-server Deployment name. + OperandBitwardenSDKServerDeployment = "bitwarden-sdk-server" // OperandCoreControllerContainer is the core controller container name. OperandCoreControllerContainer = externalsecretsCommonName @@ -110,6 +112,19 @@ const ( // containing the image version of the bitwarden-sdk-server as value. bitwardenImageVersionEnvVarName = "BITWARDEN_SDK_SERVER_IMAGE_VERSION" + // TODO: Remove in v1.4.0. Backported to 1.1/1.2 as a temporary escape hatch; + // v1.3.0 adds ExternalSecretsConfig advancedOverrides for per-component Deployment + // overrides and is the migration window before these env vars are removed. + // + // OperandExternalSecretsArgsEnvVar is the operator env var for core controller container args overrides. + OperandExternalSecretsArgsEnvVar = "OPERAND_EXTERNAL_SECRETS_ARGS" + // OperandWebhookArgsEnvVar is the operator env var for webhook container args overrides. + OperandWebhookArgsEnvVar = "OPERAND_WEBHOOK_ARGS" + // OperandCertControllerArgsEnvVar is the operator env var for cert-controller container args overrides. + OperandCertControllerArgsEnvVar = "OPERAND_CERT_CONTROLLER_ARGS" + // OperandBitwardenSDKServerArgsEnvVar is the operator env var for bitwarden-sdk-server container args overrides. + OperandBitwardenSDKServerArgsEnvVar = "OPERAND_BITWARDEN_SDK_SERVER_ARGS" + // certmanagerTLSSecretWebhook is the TLS secret created by cert-manager for the webhook component. A different // name is used to avoiding clash with the secret created by the inbuilt cert-controller component. certmanagerTLSSecretWebhook = "external-secrets-webhook-cm" diff --git a/pkg/controller/external_secrets/deployments.go b/pkg/controller/external_secrets/deployments.go index 3f2ed58e7..be04f07ed 100644 --- a/pkg/controller/external_secrets/deployments.go +++ b/pkg/controller/external_secrets/deployments.go @@ -5,6 +5,7 @@ import ( "maps" "os" "slices" + "strings" "time" "unsafe" @@ -130,6 +131,9 @@ func (r *Reconciler) getDeploymentObject(assetName string, esc *operatorv1alpha1 switch assetName { case controllerDeploymentAssetName: r.updateContainerSpec(deployment, esc, image, logLevel) + if err := applyOperandArgsFromEnv(deployment, OperandCoreControllerContainer, OperandExternalSecretsArgsEnvVar); err != nil { + return nil, fmt.Errorf("failed to apply operand args from env: %w", err) + } if err := r.applyUserCABundleConfig(deployment, esc); err != nil { wrapped := fmt.Errorf("failed to apply user CA bundle config: %w", err) // When the referenced ConfigMap is missing, the deployment spec is updated to remove @@ -146,12 +150,21 @@ func (r *Reconciler) getDeploymentObject(assetName string, esc *operatorv1alpha1 checkInterval = normalizeDurationArg(esc.Spec.ApplicationConfig.WebhookConfig.CertificateCheckInterval.Duration.String()) } updateWebhookContainerSpec(deployment, image, logLevel, checkInterval) + if err := applyOperandArgsFromEnv(deployment, OperandWebhookContainer, OperandWebhookArgsEnvVar); err != nil { + return nil, fmt.Errorf("failed to apply operand args from env: %w", err) + } updateWebhookVolumeConfig(deployment, esc) case certControllerDeploymentAssetName: updateCertControllerContainerSpec(deployment, image, logLevel) + if err := applyOperandArgsFromEnv(deployment, OperandCertControllerContainer, OperandCertControllerArgsEnvVar); err != nil { + return nil, fmt.Errorf("failed to apply operand args from env: %w", err) + } case bitwardenDeploymentAssetName: deployment.Labels["app.kubernetes.io/version"] = os.Getenv(bitwardenImageVersionEnvVarName) updateBitwardenServerContainerSpec(deployment, bitwardenImage) + if err := applyOperandArgsFromEnv(deployment, OperandBitwardenContainer, OperandBitwardenSDKServerArgsEnvVar); err != nil { + return nil, fmt.Errorf("failed to apply operand args from env: %w", err) + } updateBitwardenVolumeConfig(deployment, esc) } @@ -792,6 +805,100 @@ func (r *Reconciler) removeUserCABundleConfig(deployment *appsv1.Deployment) { } } +// argFlagKey returns the flag key for a container arg (everything before the first '='), +// or the whole token when there is no '='. Positional args without a leading "--" return "". +func argFlagKey(arg string) string { + if !strings.HasPrefix(arg, "--") { + return "" + } + if i := strings.IndexByte(arg, '='); i >= 0 { + return arg[:i] + } + return arg +} + +// parseOperandArgsEnv parses a comma-separated list of full CLI flags +// (e.g. "--concurrent=5,--loglevel=debug"). Empty segments (from trailing or +// repeated commas) are skipped. Callers should treat a fully empty/whitespace +// env value as a no-op before calling this helper. +func parseOperandArgsEnv(raw string) ([]string, error) { + parts := strings.Split(raw, ",") + args := make([]string, 0, len(parts)) + for _, part := range parts { + part = strings.TrimSpace(part) + if part == "" { + continue + } + if !strings.HasPrefix(part, "--") { + return nil, common.NewUserConfigurationError( + fmt.Errorf("argument %q must start with --", part), + "invalid custom arg override", + ) + } + args = append(args, part) + } + return args, nil +} + +// mergeContainerArgs overrides matching --flag keys in base with overrides and appends +// unknown keys. Positional args (no leading "--") in base are preserved in place; +// non-flag overrides are skipped (parseOperandArgsEnv already rejects them for the env path). +func mergeContainerArgs(base []string, overrides []string) []string { + if len(overrides) == 0 { + return base + } + + keyIndex := make(map[string]int, len(base)) + for i, arg := range base { + if key := argFlagKey(arg); key != "" { + keyIndex[key] = i + } + } + + result := slices.Clone(base) + for _, override := range overrides { + key := argFlagKey(override) + if key == "" { + continue + } + if idx, ok := keyIndex[key]; ok { + result[idx] = override + continue + } + result = append(result, override) + keyIndex[key] = len(result) - 1 + } + return result +} + +// applyOperandArgsFromEnv reads envVarName and merges its comma-separated --key=value +// flags into the named container's Args. Unset or empty env is a no-op. +// +// TODO: Remove in v1.4.0. Backported to 1.1/1.2 as a temporary escape hatch; +// v1.3.0 adds ExternalSecretsConfig advancedOverrides for per-component Deployment +// overrides and is the migration window before this env-based path is removed. +func applyOperandArgsFromEnv(deployment *appsv1.Deployment, containerName, envVarName string) error { + raw := strings.TrimSpace(os.Getenv(envVarName)) + if raw == "" { + return nil + } + + overrides, err := parseOperandArgsEnv(raw) + if err != nil { + return fmt.Errorf("%s: %w", envVarName, err) + } + + for i := range deployment.Spec.Template.Spec.Containers { + if deployment.Spec.Template.Spec.Containers[i].Name != containerName { + continue + } + deployment.Spec.Template.Spec.Containers[i].Args = mergeContainerArgs( + deployment.Spec.Template.Spec.Containers[i].Args, overrides) + return nil + } + return fmt.Errorf("container %s not found in deployment %s", containerName, deployment.GetName()) +} + // applyUserDeploymentConfigs updates the deployment resource spec with user specified configurations. func (r *Reconciler) applyUserDeploymentConfigs(deployment *appsv1.Deployment, esc *operatorv1alpha1.ExternalSecretsConfig, assetName string) error { componentName, containerName, err := getComponentNameFromAsset(assetName) diff --git a/pkg/controller/external_secrets/deployments_test.go b/pkg/controller/external_secrets/deployments_test.go index 8c21569b9..6de6cfcf6 100644 --- a/pkg/controller/external_secrets/deployments_test.go +++ b/pkg/controller/external_secrets/deployments_test.go @@ -2473,3 +2473,416 @@ func TestCreateOrApplyDeploymentFromAssetReturnsTrustedCAError(t *testing.T) { } }) } + +func TestParseOperandArgsEnv(t *testing.T) { + tests := []struct { + name string + raw string + want []string + wantErr bool + }{ + {name: "empty", raw: "", want: []string{}}, + {name: "whitespace only", raw: " \t ", want: []string{}}, + { + name: "single flag", + raw: "--concurrent=5", + want: []string{"--concurrent=5"}, + }, + { + name: "multiple flags with spaces", + raw: "--concurrent=5, --loglevel=debug", + want: []string{"--concurrent=5", "--loglevel=debug"}, + }, + { + name: "boolean style without equals", + raw: "--enable-foo,--enable-bar=true", + want: []string{"--enable-foo", "--enable-bar=true"}, + }, + { + name: "empty segments skipped", + raw: "--concurrent=5,,--loglevel=debug,", + want: []string{"--concurrent=5", "--loglevel=debug"}, + }, + {name: "missing dashes", raw: "concurrent=5", wantErr: true}, + {name: "single dash rejected", raw: "-concurrent=5", wantErr: true}, + {name: "positional token rejected", raw: "webhook,--port=10250", wantErr: true}, + {name: "positional only rejected", raw: "certcontroller", wantErr: true}, + {name: "bare equals rejected", raw: "=value", wantErr: true}, + {name: "valid then positional rejected", raw: "--port=10250,webhook", wantErr: true}, + {name: "positional mid-list rejected", raw: "--concurrent=5,not-a-flag,--loglevel=debug", wantErr: true}, + {name: "single dash mid-list rejected", raw: "--concurrent=5,-v,--loglevel=debug", wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parseOperandArgsEnv(tt.raw) + if tt.wantErr { + if err == nil { + t.Fatalf("parseOperandArgsEnv(%q) error = nil, want error", tt.raw) + } + if !common.IsUserConfigurationError(err) { + t.Fatalf("parseOperandArgsEnv(%q) error = %v, want UserConfigurationError", tt.raw, err) + } + if !strings.Contains(err.Error(), "invalid custom arg override") { + t.Fatalf("parseOperandArgsEnv(%q) error = %v, want message %q", tt.raw, err, "invalid custom arg override") + } + if !strings.Contains(err.Error(), "must start with --") { + t.Fatalf("parseOperandArgsEnv(%q) error = %v, want cause mentioning must start with --", tt.raw, err) + } + return + } + if err != nil { + t.Fatalf("parseOperandArgsEnv(%q) unexpected error: %v", tt.raw, err) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("parseOperandArgsEnv(%q) = %#v, want %#v", tt.raw, got, tt.want) + } + }) + } +} + +func TestMergeContainerArgs(t *testing.T) { + tests := []struct { + name string + base []string + overrides []string + want []string + }{ + { + name: "nil overrides unchanged", + base: []string{"--concurrent=1"}, + overrides: nil, + want: []string{"--concurrent=1"}, + }, + { + name: "override existing key", + base: []string{"--concurrent=1", "--metrics-addr=:8080"}, + overrides: []string{"--concurrent=5"}, + want: []string{"--concurrent=5", "--metrics-addr=:8080"}, + }, + { + name: "append new key", + base: []string{"--concurrent=1"}, + overrides: []string{"--enable-foo=true"}, + want: []string{"--concurrent=1", "--enable-foo=true"}, + }, + { + name: "boolean token replaces valued flag with same key", + base: []string{"--enable-foo=true", "--loglevel=info"}, + overrides: []string{"--enable-foo"}, + want: []string{"--enable-foo", "--loglevel=info"}, + }, + { + name: "preserve leading positional token", + base: []string{"webhook", "--port=10250", "--loglevel=info"}, + overrides: []string{"--loglevel=debug", "--metrics-addr=:9090"}, + want: []string{"webhook", "--port=10250", "--loglevel=debug", "--metrics-addr=:9090"}, + }, + { + name: "preserve certcontroller positional token", + base: []string{"certcontroller", "--crd-requeue-interval=5m"}, + overrides: []string{"--crd-requeue-interval=10m"}, + want: []string{"certcontroller", "--crd-requeue-interval=10m"}, + }, + { + name: "skip non-flag overrides", + base: []string{"webhook", "--port=10250"}, + overrides: []string{"webhook", "--port=10251"}, + want: []string{"webhook", "--port=10251"}, + }, + { + name: "all non-flag overrides leave base unchanged", + base: []string{"webhook", "--port=10250"}, + overrides: []string{"webhook", "certcontroller", "-v"}, + want: []string{"webhook", "--port=10250"}, + }, + { + name: "empty override tokens skipped", + base: []string{"--concurrent=1"}, + overrides: []string{"", "--concurrent=2", ""}, + want: []string{"--concurrent=2"}, + }, + { + name: "single-dash override skipped", + base: []string{"--loglevel=info"}, + overrides: []string{"-loglevel=debug", "--metrics-addr=:9090"}, + want: []string{"--loglevel=info", "--metrics-addr=:9090"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := mergeContainerArgs(tt.base, tt.overrides) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("mergeContainerArgs() = %#v, want %#v", got, tt.want) + } + }) + } +} + +func TestApplyOperandArgsFromEnv(t *testing.T) { + deploymentWithContainer := func(name string, args []string) *appsv1.Deployment { + return &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{Name: name, Args: args}}, + }, + }, + }, + } + } + + t.Run("unset env is no-op", func(t *testing.T) { + dep := deploymentWithContainer(OperandCoreControllerContainer, []string{"--concurrent=1"}) + if err := applyOperandArgsFromEnv(dep, OperandCoreControllerContainer, OperandExternalSecretsArgsEnvVar); err != nil { + t.Fatalf("unexpected error: %v", err) + } + want := []string{"--concurrent=1"} + if !reflect.DeepEqual(dep.Spec.Template.Spec.Containers[0].Args, want) { + t.Errorf("Args = %#v, want %#v", dep.Spec.Template.Spec.Containers[0].Args, want) + } + }) + + t.Run("empty env is no-op", func(t *testing.T) { + t.Setenv(OperandExternalSecretsArgsEnvVar, " ") + dep := deploymentWithContainer(OperandCoreControllerContainer, []string{"--concurrent=1"}) + if err := applyOperandArgsFromEnv(dep, OperandCoreControllerContainer, OperandExternalSecretsArgsEnvVar); err != nil { + t.Fatalf("unexpected error: %v", err) + } + want := []string{"--concurrent=1"} + if !reflect.DeepEqual(dep.Spec.Template.Spec.Containers[0].Args, want) { + t.Errorf("Args = %#v, want %#v", dep.Spec.Template.Spec.Containers[0].Args, want) + } + }) + + t.Run("overrides and appends", func(t *testing.T) { + t.Setenv(OperandExternalSecretsArgsEnvVar, "--concurrent=5,--enable-foo=true") + dep := deploymentWithContainer(OperandCoreControllerContainer, []string{"--concurrent=1", "--metrics-addr=:8080"}) + if err := applyOperandArgsFromEnv(dep, OperandCoreControllerContainer, OperandExternalSecretsArgsEnvVar); err != nil { + t.Fatalf("unexpected error: %v", err) + } + want := []string{"--concurrent=5", "--metrics-addr=:8080", "--enable-foo=true"} + if !reflect.DeepEqual(dep.Spec.Template.Spec.Containers[0].Args, want) { + t.Errorf("Args = %#v, want %#v", dep.Spec.Template.Spec.Containers[0].Args, want) + } + }) + + t.Run("invalid entry fails", func(t *testing.T) { + t.Setenv(OperandWebhookArgsEnvVar, "not-a-flag") + dep := deploymentWithContainer(OperandWebhookContainer, []string{"webhook", "--port=10250"}) + err := applyOperandArgsFromEnv(dep, OperandWebhookContainer, OperandWebhookArgsEnvVar) + if err == nil { + t.Fatal("expected error for invalid args env value") + } + if !common.IsUserConfigurationError(err) { + t.Fatalf("error = %v, want UserConfigurationError", err) + } + wantSubstrings := []string{ + OperandWebhookArgsEnvVar, + "invalid custom arg override", + `argument "not-a-flag" must start with --`, + } + for _, sub := range wantSubstrings { + if !strings.Contains(err.Error(), sub) { + t.Fatalf("error = %v, want substring %q", err, sub) + } + } + }) + + t.Run("positional env value fails without mutating args", func(t *testing.T) { + t.Setenv(OperandWebhookArgsEnvVar, "webhook,--port=10251") + original := []string{"webhook", "--port=10250"} + dep := deploymentWithContainer(OperandWebhookContainer, append([]string(nil), original...)) + err := applyOperandArgsFromEnv(dep, OperandWebhookContainer, OperandWebhookArgsEnvVar) + if err == nil { + t.Fatal("expected error for positional env value") + } + if !common.IsUserConfigurationError(err) { + t.Fatalf("error = %v, want UserConfigurationError", err) + } + if !reflect.DeepEqual(dep.Spec.Template.Spec.Containers[0].Args, original) { + t.Errorf("Args mutated on error: %#v, want %#v", dep.Spec.Template.Spec.Containers[0].Args, original) + } + }) + + t.Run("single dash env value fails", func(t *testing.T) { + t.Setenv(OperandExternalSecretsArgsEnvVar, "-concurrent=5") + dep := deploymentWithContainer(OperandCoreControllerContainer, []string{"--concurrent=1"}) + err := applyOperandArgsFromEnv(dep, OperandCoreControllerContainer, OperandExternalSecretsArgsEnvVar) + if err == nil { + t.Fatal("expected error for single-dash env value") + } + if !common.IsUserConfigurationError(err) { + t.Fatalf("error = %v, want UserConfigurationError", err) + } + }) + + t.Run("invalid entry after valid flags fails", func(t *testing.T) { + t.Setenv(OperandExternalSecretsArgsEnvVar, "--concurrent=5,bad") + original := []string{"--concurrent=1"} + dep := deploymentWithContainer(OperandCoreControllerContainer, append([]string(nil), original...)) + err := applyOperandArgsFromEnv(dep, OperandCoreControllerContainer, OperandExternalSecretsArgsEnvVar) + if err == nil { + t.Fatal("expected error for mixed valid/invalid env value") + } + if !common.IsUserConfigurationError(err) { + t.Fatalf("error = %v, want UserConfigurationError", err) + } + if !reflect.DeepEqual(dep.Spec.Template.Spec.Containers[0].Args, original) { + t.Errorf("Args mutated on error: %#v, want %#v", dep.Spec.Template.Spec.Containers[0].Args, original) + } + }) + + t.Run("missing container fails", func(t *testing.T) { + t.Setenv(OperandExternalSecretsArgsEnvVar, "--concurrent=5") + dep := deploymentWithContainer("other", []string{"--concurrent=1"}) + if err := applyOperandArgsFromEnv(dep, OperandCoreControllerContainer, OperandExternalSecretsArgsEnvVar); err == nil { + t.Fatal("expected error for missing container") + } + }) + + t.Run("empty containers list fails", func(t *testing.T) { + t.Setenv(OperandExternalSecretsArgsEnvVar, "--concurrent=5") + dep := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "test"}} + if err := applyOperandArgsFromEnv(dep, OperandCoreControllerContainer, OperandExternalSecretsArgsEnvVar); err == nil { + t.Fatal("expected error for empty containers list") + } + }) + + t.Run("bitwarden args applied onto empty base", func(t *testing.T) { + t.Setenv(OperandBitwardenSDKServerArgsEnvVar, "--port=9999") + dep := deploymentWithContainer(OperandBitwardenContainer, nil) + if err := applyOperandArgsFromEnv(dep, OperandBitwardenContainer, OperandBitwardenSDKServerArgsEnvVar); err != nil { + t.Fatalf("unexpected error: %v", err) + } + want := []string{"--port=9999"} + if !reflect.DeepEqual(dep.Spec.Template.Spec.Containers[0].Args, want) { + t.Errorf("Args = %#v, want %#v", dep.Spec.Template.Spec.Containers[0].Args, want) + } + }) +} + +func TestGetDeploymentObjectOperandArgsFromEnv(t *testing.T) { + t.Setenv(externalsecretsImageEnvVarName, commontest.TestExternalSecretsImageName) + t.Setenv(bitwardenImageEnvVarName, commontest.TestBitwardenImageName) + + esc := commontest.TestExternalSecretsConfig() + resourceMetadata := testResourceMetadata(esc) + r := testReconciler(t) + + t.Run("controller overrides concurrent", func(t *testing.T) { + t.Setenv(OperandExternalSecretsArgsEnvVar, "--concurrent=5") + dep, err := r.getDeploymentObject(controllerDeploymentAssetName, esc, resourceMetadata) + if err != nil { + t.Fatalf("getDeploymentObject() unexpected error: %v", err) + } + args := containerArgsByName(dep, OperandCoreControllerContainer) + if !slices.Contains(args, "--concurrent=5") { + t.Errorf("expected --concurrent=5 in args, got %#v", args) + } + if slices.Contains(args, "--concurrent=1") { + t.Errorf("default --concurrent=1 should have been overridden, got %#v", args) + } + }) + + t.Run("webhook preserves positional token", func(t *testing.T) { + t.Setenv(OperandWebhookArgsEnvVar, "--loglevel=debug") + dep, err := r.getDeploymentObject(webhookDeploymentAssetName, esc, resourceMetadata) + if err != nil { + t.Fatalf("getDeploymentObject() unexpected error: %v", err) + } + args := containerArgsByName(dep, OperandWebhookContainer) + if len(args) == 0 || args[0] != "webhook" { + t.Errorf("expected leading webhook token, got %#v", args) + } + if !slices.Contains(args, "--loglevel=debug") { + t.Errorf("expected --loglevel=debug in args, got %#v", args) + } + }) + + t.Run("cert controller preserves positional token", func(t *testing.T) { + t.Setenv(OperandCertControllerArgsEnvVar, "--crd-requeue-interval=10m") + dep, err := r.getDeploymentObject(certControllerDeploymentAssetName, esc, resourceMetadata) + if err != nil { + t.Fatalf("getDeploymentObject() unexpected error: %v", err) + } + args := containerArgsByName(dep, OperandCertControllerContainer) + if len(args) == 0 || args[0] != "certcontroller" { + t.Errorf("expected leading certcontroller token, got %#v", args) + } + if !slices.Contains(args, "--crd-requeue-interval=10m") { + t.Errorf("expected overridden interval in args, got %#v", args) + } + }) + + t.Run("bitwarden applies args", func(t *testing.T) { + escWithBW := esc.DeepCopy() + escWithBW.Spec.Plugins.BitwardenSecretManagerProvider = &v1alpha1.BitwardenSecretManagerProvider{ + Mode: v1alpha1.Enabled, + SecretRef: &v1alpha1.SecretReference{ + Name: "bitwarden-certs", + }, + } + t.Setenv(OperandBitwardenSDKServerArgsEnvVar, "--enable-debug=true") + dep, err := r.getDeploymentObject(bitwardenDeploymentAssetName, escWithBW, resourceMetadata) + if err != nil { + t.Fatalf("getDeploymentObject() unexpected error: %v", err) + } + args := containerArgsByName(dep, OperandBitwardenContainer) + if !reflect.DeepEqual(args, []string{"--enable-debug=true"}) { + t.Errorf("Args = %#v, want [--enable-debug=true]", args) + } + }) + + t.Run("invalid env fails", func(t *testing.T) { + t.Setenv(OperandExternalSecretsArgsEnvVar, "bad-arg") + _, err := r.getDeploymentObject(controllerDeploymentAssetName, esc, resourceMetadata) + if err == nil { + t.Fatal("expected error for invalid operand args env") + } + if !common.IsUserConfigurationError(err) { + t.Fatalf("error = %v, want UserConfigurationError", err) + } + wantSubstrings := []string{ + OperandExternalSecretsArgsEnvVar, + "invalid custom arg override", + `argument "bad-arg" must start with --`, + } + for _, sub := range wantSubstrings { + if !strings.Contains(err.Error(), sub) { + t.Fatalf("error = %v, want substring %q", err, sub) + } + } + }) + + t.Run("webhook positional env fails", func(t *testing.T) { + t.Setenv(OperandWebhookArgsEnvVar, "webhook,--port=10251") + _, err := r.getDeploymentObject(webhookDeploymentAssetName, esc, resourceMetadata) + if err == nil { + t.Fatal("expected error for positional webhook env value") + } + if !common.IsUserConfigurationError(err) { + t.Fatalf("error = %v, want UserConfigurationError", err) + } + }) + + t.Run("cert controller single dash env fails", func(t *testing.T) { + t.Setenv(OperandCertControllerArgsEnvVar, "-crd-requeue-interval=10m") + _, err := r.getDeploymentObject(certControllerDeploymentAssetName, esc, resourceMetadata) + if err == nil { + t.Fatal("expected error for single-dash cert-controller env value") + } + if !common.IsUserConfigurationError(err) { + t.Fatalf("error = %v, want UserConfigurationError", err) + } + }) +} + +func containerArgsByName(dep *appsv1.Deployment, name string) []string { + for i := range dep.Spec.Template.Spec.Containers { + if dep.Spec.Template.Spec.Containers[i].Name == name { + return dep.Spec.Template.Spec.Containers[i].Args + } + } + return nil +} diff --git a/test/e2e/README.md b/test/e2e/README.md index 0524987fa..c42df58bc 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -28,6 +28,7 @@ make test-e2e E2E_GINKGO_LABEL_FILTER="" | Value | Test area | |-------|-----------| | `OverrideEnv` | Custom env vars on operand deployments | +| `OverrideOperandArgs` | Operator `OPERAND_*_ARGS` env overrides for operand Deployments | | `RevisionHistoryLimit` | Deployment revision history limits | | `UnsafeAllowGenericTargets` | ExternalSecretsManager feature gate propagation | | `CustomAnnotations` | Annotation apply/remove and managed-annotation restoration | @@ -93,6 +94,7 @@ If a prerequisite is missing, the affected spec **fails** with a message pointin | `Feature:Upgrade` | Post-upgrade network policy migration check | | `Feature:NetworkPolicy` | Static and custom network policy naming | | `Feature:OverrideEnv` | Component override env vars | +| `Feature:OverrideOperandArgs` | Operator `OPERAND_*_ARGS` env overrides for operand Deployments | | `Feature:RevisionHistoryLimit` | Revision history limit defaults and overrides | | `Feature:UnsafeAllowGenericTargets` | UnsafeAllowGenericTargets feature propagation | | `Feature:CustomAnnotations` | Annotation lifecycle tests | @@ -143,6 +145,12 @@ File: `e2e_test.go` | `NetworkPolicy` + `Upgrade` | Post-upgrade skip-np-cleanup-check annotation (also tagged `Feature:Upgrade`) | | `Proxy` | Proxy Egress Network Policy | +File: `operand_args_test.go` + +| Feature | Describe | +|---------|----------| +| `OverrideOperandArgs` | Operand Args Env Overrides | + File: `trusted_ca_bundle_test.go` | Feature | Describe | diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go index c0de57ef3..8b07633fe 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/e2e_suite_test.go @@ -38,6 +38,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" operatorv1alpha1 "github.com/openshift/external-secrets-operator/api/v1alpha1" "github.com/openshift/external-secrets-operator/test/utils" @@ -78,6 +79,7 @@ var _ = BeforeSuite(func() { scheme := runtime.NewScheme() utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(operatorv1alpha1.AddToScheme(scheme)) + utilruntime.Must(olmv1alpha1.AddToScheme(scheme)) suiteRuntimeClient, err = client.New(cfg, client.Options{Scheme: scheme}) Expect(err).NotTo(HaveOccurred(), "failed to create runtime client") }) diff --git a/test/e2e/helpers_test.go b/test/e2e/helpers_test.go index 6117ca406..b3cc50409 100644 --- a/test/e2e/helpers_test.go +++ b/test/e2e/helpers_test.go @@ -22,15 +22,20 @@ import ( "context" "fmt" "slices" + "strings" "testing" "time" . "github.com/onsi/gomega" + olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + "k8s.io/client-go/util/retry" "sigs.k8s.io/controller-runtime/pkg/client" operatorv1alpha1 "github.com/openshift/external-secrets-operator/api/v1alpha1" @@ -38,6 +43,16 @@ import ( "github.com/openshift/external-secrets-operator/test/utils" ) +const ( + operatorDeploymentName = common.ExternalSecretsOperatorCommonName + "-controller-manager" + operatorManagerContainerName = "manager" + // operatorCSVNamePrefix matches ClusterServiceVersion names like + // openshift-external-secrets-operator.v1.2.0. + operatorCSVNamePrefix = "openshift-external-secrets-operator." + // operatorPackageName is the OLM package / Subscription.spec.name value. + operatorPackageName = "openshift-external-secrets-operator" +) + // ensureExternalSecretsConfigReady creates the cluster ExternalSecretsConfig CR when missing // and waits until Ready=True. Shared by suite Describes that may run before e2e_test BeforeAll. func ensureExternalSecretsConfigReady(ctx context.Context) error { @@ -229,3 +244,297 @@ func deploymentContainerHasArg(deployment *appsv1.Deployment, containerName, arg } return slices.Contains(args, arg), true } + +// setOperatorManagerEnv sets or updates env vars on the operator manager container. +// Prefer updating Subscription.spec.config.env (OLM-supported) when a matching CSV +// and Subscription exist; otherwise update the Deployment directly. +// Works for any manager env (OPERAND_*_ARGS, OPERATOR_LOG_LEVEL, METRICS_*, etc.). +// OLM rolls a new manager pod after Subscription updates; the startup reconcile reads +// process env and applies operand args. This helper waits until that Ready pod has the +// desired env before returning. +func setOperatorManagerEnv(ctx context.Context, clientset kubernetes.Interface, c client.Client, envVars map[string]string) error { + if len(envVars) == 0 { + return nil + } + updatedViaSub, err := updateSubscriptionEnv(ctx, clientset, c, envVars, nil) + if err != nil { + return err + } + if !updatedViaSub { + if err := updateOperatorDeploymentEnv(ctx, clientset, envVars, nil); err != nil { + return err + } + } + waitForOperatorManagerEnv(ctx, clientset, envVars, nil) + return nil +} + +// unsetOperatorManagerEnv removes env vars from the operator manager container. +func unsetOperatorManagerEnv(ctx context.Context, clientset kubernetes.Interface, c client.Client, keys []string) error { + if len(keys) == 0 { + return nil + } + updatedViaSub, err := updateSubscriptionEnv(ctx, clientset, c, nil, keys) + if err != nil { + return err + } + if !updatedViaSub { + if err := updateOperatorDeploymentEnv(ctx, clientset, nil, keys); err != nil { + return err + } + } + waitForOperatorManagerEnv(ctx, clientset, nil, keys) + return nil +} + +// updateSubscriptionEnv merges env into Subscription.spec.config.env using the CSV to +// locate the Subscription namespace. Returns false when no OLM Subscription is found. +func updateSubscriptionEnv(ctx context.Context, clientset kubernetes.Interface, c client.Client, set map[string]string, unset []string) (bool, error) { + csv, err := findOperatorCSV(ctx, clientset, c) + if err != nil { + return false, err + } + if csv == nil { + return false, nil + } + + subNamespace := csv.Annotations[olmv1alpha1.OperatorGroupNamespaceAnnotationKey] + if subNamespace == "" { + subNamespace = csv.Namespace + } + if subNamespace == "" { + return false, fmt.Errorf("CSV %s has empty namespace and no %s annotation", csv.Name, olmv1alpha1.OperatorGroupNamespaceAnnotationKey) + } + + sub, err := findOperatorSubscription(ctx, c, subNamespace) + if err != nil { + return false, err + } + if sub == nil { + return false, nil + } + + err = retry.RetryOnConflict(retry.DefaultRetry, func() error { + current := &olmv1alpha1.Subscription{} + if err := c.Get(ctx, client.ObjectKey{Namespace: subNamespace, Name: sub.Name}, current); err != nil { + return fmt.Errorf("get Subscription %s/%s: %w", subNamespace, sub.Name, err) + } + + var existing []corev1.EnvVar + if current.Spec.Config != nil { + existing = current.Spec.Config.Env + } + merged := mergeEnvVars(existing, set, unset) + if current.Spec.Config == nil { + if len(merged) == 0 { + return nil + } + current.Spec.Config = &olmv1alpha1.SubscriptionConfig{} + } + current.Spec.Config.Env = merged + if isEmptySubscriptionConfig(current.Spec.Config) { + current.Spec.Config = nil + } + + return c.Update(ctx, current) + }) + if err != nil { + return false, err + } + return true, nil +} + +// findOperatorCSV returns the installed openshift-external-secrets-operator CSV, or nil +// when the operator is not managed by OLM. +func findOperatorCSV(ctx context.Context, clientset kubernetes.Interface, c client.Client) (*olmv1alpha1.ClusterServiceVersion, error) { + var list olmv1alpha1.ClusterServiceVersionList + if err := c.List(ctx, &list, client.InNamespace(operatorNamespace)); err != nil { + // NotFound / NoMatch: OLM CRDs missing or no CSVs — fall through to Deployment path. + if !k8serrors.IsNotFound(err) && !meta.IsNoMatchError(err) { + return nil, fmt.Errorf("list CSVs in %s: %w", operatorNamespace, err) + } + } + for i := range list.Items { + if strings.HasPrefix(list.Items[i].Name, operatorCSVNamePrefix) { + return list.Items[i].DeepCopy(), nil + } + } + + // Fallback: resolve CSV from the operator Deployment ownerReference. + dep, err := clientset.AppsV1().Deployments(operatorNamespace).Get(ctx, operatorDeploymentName, metav1.GetOptions{}) + if err != nil { + if k8serrors.IsNotFound(err) { + return nil, nil + } + return nil, fmt.Errorf("get operator deployment: %w", err) + } + for _, ref := range dep.OwnerReferences { + if ref.Kind != "ClusterServiceVersion" || ref.Name == "" { + continue + } + csv := &olmv1alpha1.ClusterServiceVersion{} + if err := c.Get(ctx, client.ObjectKey{Namespace: operatorNamespace, Name: ref.Name}, csv); err != nil { + if meta.IsNoMatchError(err) { + return nil, nil + } + if k8serrors.IsNotFound(err) { + continue + } + return nil, fmt.Errorf("get CSV %s/%s: %w", operatorNamespace, ref.Name, err) + } + return csv, nil + } + return nil, nil +} + +// findOperatorSubscription returns the Subscription for the operator package in ns. +func findOperatorSubscription(ctx context.Context, c client.Client, ns string) (*olmv1alpha1.Subscription, error) { + var list olmv1alpha1.SubscriptionList + if err := c.List(ctx, &list, client.InNamespace(ns)); err != nil { + return nil, fmt.Errorf("list Subscriptions in %s: %w", ns, err) + } + for i := range list.Items { + item := &list.Items[i] + if item.Spec.Package == operatorPackageName || strings.HasPrefix(item.Name, operatorPackageName) { + return item.DeepCopy(), nil + } + } + return nil, nil +} + +func isEmptySubscriptionConfig(c *olmv1alpha1.SubscriptionConfig) bool { + if c == nil { + return true + } + return c.Selector == nil && + len(c.NodeSelector) == 0 && + len(c.Tolerations) == 0 && + c.Resources == nil && + len(c.EnvFrom) == 0 && + len(c.Env) == 0 && + len(c.Volumes) == 0 && + len(c.VolumeMounts) == 0 && + c.Affinity == nil && + len(c.Annotations) == 0 +} + +func updateOperatorDeploymentEnv(ctx context.Context, clientset kubernetes.Interface, set map[string]string, unset []string) error { + return retry.RetryOnConflict(retry.DefaultRetry, func() error { + dep, err := clientset.AppsV1().Deployments(operatorNamespace).Get(ctx, operatorDeploymentName, metav1.GetOptions{}) + if err != nil { + return err + } + idx := managerContainerIndex(dep.Spec.Template.Spec.Containers) + if idx < 0 { + return fmt.Errorf("manager container not found in operator deployment") + } + dep.Spec.Template.Spec.Containers[idx].Env = mergeEnvVars(dep.Spec.Template.Spec.Containers[idx].Env, set, unset) + _, err = clientset.AppsV1().Deployments(operatorNamespace).Update(ctx, dep, metav1.UpdateOptions{}) + return err + }) +} + +func managerContainerIndex(containers []corev1.Container) int { + for i, c := range containers { + if c.Name == operatorManagerContainerName { + return i + } + } + return -1 +} + +func mergeEnvVars(existing []corev1.EnvVar, set map[string]string, unset []string) []corev1.EnvVar { + remove := make(map[string]struct{}, len(unset)) + for _, k := range unset { + remove[k] = struct{}{} + } + out := make([]corev1.EnvVar, 0, len(existing)+len(set)) + seen := make(map[string]bool, len(existing)) + for _, env := range existing { + if _, drop := remove[env.Name]; drop { + continue + } + if val, ok := set[env.Name]; ok { + env.Value = val + env.ValueFrom = nil + } + out = append(out, env) + seen[env.Name] = true + } + // Sort newly appended names so repeated merges produce a stable Env order + // (map iteration order is nondeterministic and can trigger an extra OLM rollout). + toAdd := make([]string, 0, len(set)) + for name := range set { + if !seen[name] { + toAdd = append(toAdd, name) + } + } + slices.Sort(toAdd) + for _, name := range toAdd { + out = append(out, corev1.EnvVar{Name: name, Value: set[name]}) + } + return out +} + +func waitForOperatorManagerEnv(ctx context.Context, clientset kubernetes.Interface, want map[string]string, unset []string) { + Eventually(func(g Gomega) { + dep, err := clientset.AppsV1().Deployments(operatorNamespace).Get(ctx, operatorDeploymentName, metav1.GetOptions{}) + g.Expect(err).NotTo(HaveOccurred()) + idx := managerContainerIndex(dep.Spec.Template.Spec.Containers) + g.Expect(idx).To(BeNumerically(">=", 0), "manager container should exist") + assertEnvMap(g, envSliceToMap(dep.Spec.Template.Spec.Containers[idx].Env), want, unset, "operator Deployment") + + // Deployment env can update before the rolled pod is the Ready one; os.Getenv in the + // manager only sees the running pod's env, so wait for that too. + pods, err := clientset.CoreV1().Pods(operatorNamespace).List(ctx, metav1.ListOptions{}) + g.Expect(err).NotTo(HaveOccurred()) + var readyPod *corev1.Pod + for i := range pods.Items { + pod := &pods.Items[i] + if pod.DeletionTimestamp != nil || !strings.HasPrefix(pod.Name, operatorPodPrefix) { + continue + } + if pod.Status.Phase == corev1.PodRunning && isOperatorPodReady(pod) { + readyPod = pod + break + } + } + g.Expect(readyPod).NotTo(BeNil(), "expected a Ready non-terminating operator manager pod") + cidx := managerContainerIndex(readyPod.Spec.Containers) + g.Expect(cidx).To(BeNumerically(">=", 0), "manager container should exist on Ready pod %s", readyPod.Name) + assertEnvMap(g, envSliceToMap(readyPod.Spec.Containers[cidx].Env), want, unset, "operator pod "+readyPod.Name) + }, 3*time.Minute, 5*time.Second).Should(Succeed()) +} + +func envSliceToMap(env []corev1.EnvVar) map[string]string { + out := make(map[string]string, len(env)) + for _, e := range env { + out[e.Name] = e.Value + } + return out +} + +func assertEnvMap(g Gomega, envMap map[string]string, want map[string]string, unset []string, where string) { + for name, val := range want { + g.Expect(envMap).To(HaveKeyWithValue(name, val), "%s should have env %s=%s", where, name, val) + } + for _, name := range unset { + g.Expect(envMap).NotTo(HaveKey(name), "%s should not have env %s", where, name) + } +} + +func isOperatorPodReady(pod *corev1.Pod) bool { + ready, containersReady := false, false + for _, cond := range pod.Status.Conditions { + if cond.Status != corev1.ConditionTrue { + continue + } + switch cond.Type { + case corev1.PodReady: + ready = true + case corev1.ContainersReady: + containersReady = true + } + } + return ready && containersReady +} diff --git a/test/e2e/operand_args_test.go b/test/e2e/operand_args_test.go new file mode 100644 index 000000000..0f13268ac --- /dev/null +++ b/test/e2e/operand_args_test.go @@ -0,0 +1,285 @@ +//go:build e2e +// +build e2e + +/* +Copyright 2025. + +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 e2e + +import ( + "context" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/util/retry" + "sigs.k8s.io/controller-runtime/pkg/client" + + operatorv1alpha1 "github.com/openshift/external-secrets-operator/api/v1alpha1" + "github.com/openshift/external-secrets-operator/pkg/controller/common" + externalsecrets "github.com/openshift/external-secrets-operator/pkg/controller/external_secrets" + "github.com/openshift/external-secrets-operator/test/utils" +) + +var _ = Describe("Operand Args Env Overrides", Ordered, Label("Platform:Generic", "Feature:OverrideOperandArgs"), func() { + ctx := context.Background() + + const ( + controllerArg = "--concurrent=2" + webhookArg = "--check-interval=10m0s" + certControllerArg = "--crd-requeue-interval=10m" + bitwardenArg = "--key-file=/certs/key.pem" + ) + + var ( + clientset *kubernetes.Clientset + dynamicClient *dynamic.DynamicClient + runtimeClient client.Client + + // originalBitwardenProvider is captured before BeforeAll enables the plugin so + // AfterAll can restore cluster-scoped ExternalSecretsConfig for later suites. + originalBitwardenProvider *operatorv1alpha1.BitwardenSecretManagerProvider + + operandArgsEnv = map[string]string{ + externalsecrets.OperandExternalSecretsArgsEnvVar: controllerArg, + externalsecrets.OperandWebhookArgsEnvVar: webhookArg, + externalsecrets.OperandCertControllerArgsEnvVar: certControllerArg, + externalsecrets.OperandBitwardenSDKServerArgsEnvVar: bitwardenArg, + } + operandArgsEnvKeys = []string{ + externalsecrets.OperandExternalSecretsArgsEnvVar, + externalsecrets.OperandWebhookArgsEnvVar, + externalsecrets.OperandCertControllerArgsEnvVar, + externalsecrets.OperandBitwardenSDKServerArgsEnvVar, + } + ) + + waitForOperandArg := func(deploymentName, containerName, arg string, present bool) { + Eventually(func(g Gomega) { + deployment, err := clientset.AppsV1().Deployments(operandNamespace).Get(ctx, deploymentName, metav1.GetOptions{}) + g.Expect(err).NotTo(HaveOccurred(), "should get %s deployment", deploymentName) + hasArg, found := deploymentContainerHasArg(deployment, containerName, arg) + g.Expect(found).To(BeTrue(), "%s container should exist in %s", containerName, deploymentName) + if present { + g.Expect(hasArg).To(BeTrue(), "%s/%s should include %q", deploymentName, containerName, arg) + } else { + g.Expect(hasArg).To(BeFalse(), "%s/%s should not include %q", deploymentName, containerName, arg) + } + }, 3*time.Minute, 5*time.Second).Should(Succeed()) + } + + BeforeAll(func() { + clientset = suiteClientset + dynamicClient = suiteDynamicClient + runtimeClient = suiteRuntimeClient + Expect(clientset).NotTo(BeNil()) + Expect(dynamicClient).NotTo(BeNil()) + Expect(runtimeClient).NotTo(BeNil()) + + By("Ensuring ExternalSecretsConfig is Ready") + Expect(ensureExternalSecretsConfigReady(ctx)).To(Succeed()) + + esc := &operatorv1alpha1.ExternalSecretsConfig{} + Expect(runtimeClient.Get(ctx, client.ObjectKey{Name: common.ExternalSecretsConfigObjectName}, esc)).To(Succeed()) + if esc.Spec.Plugins.BitwardenSecretManagerProvider != nil { + originalBitwardenProvider = esc.Spec.Plugins.BitwardenSecretManagerProvider.DeepCopy() + } + + By("Provisioning bitwarden-sdk-server so its Deployment can be verified") + Expect(ensureBitwardenOperandReady(ctx, nil)).To(Succeed()) + + By("Setting OPERAND_*_ARGS on the operator manager") + Expect(setOperatorManagerEnv(ctx, clientset, runtimeClient, operandArgsEnv)).To(Succeed()) + + By("Waiting for operator pod to be ready after env update") + Expect(utils.VerifyPodsReadyByPrefix(ctx, clientset, operatorNamespace, []string{operatorPodPrefix})).To(Succeed()) + }) + + AfterAll(func() { + By("Clearing OPERAND_*_ARGS from the operator manager") + Expect(unsetOperatorManagerEnv(ctx, clientset, runtimeClient, operandArgsEnvKeys)).To(Succeed()) + + By("Waiting for operator pod to be ready after env cleanup") + Expect(utils.VerifyPodsReadyByPrefix(ctx, clientset, operatorNamespace, []string{operatorPodPrefix})).To(Succeed()) + + By("Reverting ExternalSecretsConfig Bitwarden plugin to pre-suite state") + Expect(retry.RetryOnConflict(retry.DefaultRetry, func() error { + esc := &operatorv1alpha1.ExternalSecretsConfig{} + if err := runtimeClient.Get(ctx, client.ObjectKey{Name: common.ExternalSecretsConfigObjectName}, esc); err != nil { + return err + } + esc.Spec.Plugins.BitwardenSecretManagerProvider = originalBitwardenProvider + return runtimeClient.Update(ctx, esc) + })).To(Succeed()) + Expect(utils.WaitForExternalSecretsConfigReady(ctx, dynamicClient, common.ExternalSecretsConfigObjectName, 3*time.Minute)).To(Succeed()) + + esc := &operatorv1alpha1.ExternalSecretsConfig{} + Expect(runtimeClient.Get(ctx, client.ObjectKey{Name: common.ExternalSecretsConfigObjectName}, esc)).To(Succeed()) + By("Waiting for operand pods to be ready after args cleanup") + Expect(utils.VerifyOperandPodsReady(ctx, clientset, operandNamespace, esc)).To(Succeed()) + }) + + It("should override args on the core controller Deployment", func() { + By("Verifying controller args override and default concurrent flag is replaced") + waitForOperandArg(externalsecrets.OperandCoreControllerDeployment, externalsecrets.OperandCoreControllerContainer, controllerArg, true) + Eventually(func(g Gomega) { + deployment, err := clientset.AppsV1().Deployments(operandNamespace).Get(ctx, externalsecrets.OperandCoreControllerDeployment, metav1.GetOptions{}) + g.Expect(err).NotTo(HaveOccurred()) + hasDefault, found := deploymentContainerHasArg(deployment, externalsecrets.OperandCoreControllerContainer, "--concurrent=1") + g.Expect(found).To(BeTrue()) + g.Expect(hasDefault).To(BeFalse(), "default --concurrent=1 should be overridden") + }, 3*time.Minute, 5*time.Second).Should(Succeed()) + }) + + It("should override args on the webhook Deployment and keep the positional token", func() { + waitForOperandArg(externalsecrets.OperandWebhookDeployment, externalsecrets.OperandWebhookContainer, webhookArg, true) + Eventually(func(g Gomega) { + deployment, err := clientset.AppsV1().Deployments(operandNamespace).Get(ctx, externalsecrets.OperandWebhookDeployment, metav1.GetOptions{}) + g.Expect(err).NotTo(HaveOccurred()) + args, found := getDeploymentContainerArgs(deployment, externalsecrets.OperandWebhookContainer) + g.Expect(found).To(BeTrue()) + g.Expect(args).NotTo(BeEmpty()) + g.Expect(args[0]).To(Equal("webhook"), "webhook positional token should be preserved") + }, 3*time.Minute, 5*time.Second).Should(Succeed()) + }) + + It("should override args on the cert-controller Deployment when present", func() { + esc := &operatorv1alpha1.ExternalSecretsConfig{} + Expect(runtimeClient.Get(ctx, client.ObjectKey{Name: common.ExternalSecretsConfigObjectName}, esc)).To(Succeed()) + if !utils.IsCertControllerExpected(esc) { + Skip("cert-controller Deployment is not expected with current ExternalSecretsConfig (cert-manager enabled)") + } + + waitForOperandArg(externalsecrets.OperandCertControllerDeployment, externalsecrets.OperandCertControllerContainer, certControllerArg, true) + Eventually(func(g Gomega) { + deployment, err := clientset.AppsV1().Deployments(operandNamespace).Get(ctx, externalsecrets.OperandCertControllerDeployment, metav1.GetOptions{}) + g.Expect(err).NotTo(HaveOccurred()) + args, found := getDeploymentContainerArgs(deployment, externalsecrets.OperandCertControllerContainer) + g.Expect(found).To(BeTrue()) + g.Expect(args).NotTo(BeEmpty()) + g.Expect(args[0]).To(Equal("certcontroller"), "certcontroller positional token should be preserved") + }, 3*time.Minute, 5*time.Second).Should(Succeed()) + }) + + It("should apply args on the bitwarden-sdk-server Deployment", func() { + waitForOperandArg(externalsecrets.OperandBitwardenSDKServerDeployment, externalsecrets.OperandBitwardenContainer, bitwardenArg, true) + }) + + It("should mark ExternalSecretsConfig Degraded for invalid OPERAND_*_ARGS", func() { + By("Setting an invalid controller args override") + Expect(setOperatorManagerEnv(ctx, clientset, runtimeClient, map[string]string{ + externalsecrets.OperandExternalSecretsArgsEnvVar: "not-a-flag", + })).To(Succeed()) + Expect(utils.VerifyPodsReadyByPrefix(ctx, clientset, operatorNamespace, []string{operatorPodPrefix})).To(Succeed()) + + By("Waiting for ExternalSecretsConfig to become Degraded with a user-configuration message") + Eventually(func(g Gomega) { + g.Expect(isExternalSecretsConfigDegraded(ctx)).To(BeTrue(), + "ExternalSecretsConfig should be Degraded for invalid OPERAND_*_ARGS") + msg := externalSecretsConfigDegradedMessage(ctx) + g.Expect(msg).To(ContainSubstring("invalid custom arg override")) + g.Expect(msg).To(ContainSubstring(`argument "not-a-flag" must start with --`)) + g.Expect(msg).To(ContainSubstring(externalsecrets.OperandExternalSecretsArgsEnvVar)) + }, 3*time.Minute, 5*time.Second).Should(Succeed()) + + By("Verifying the invalid token was not applied to the controller Deployment") + Eventually(func(g Gomega) { + deployment, err := clientset.AppsV1().Deployments(operandNamespace).Get(ctx, externalsecrets.OperandCoreControllerDeployment, metav1.GetOptions{}) + g.Expect(err).NotTo(HaveOccurred()) + hasArg, found := deploymentContainerHasArg(deployment, externalsecrets.OperandCoreControllerContainer, "not-a-flag") + g.Expect(found).To(BeTrue()) + g.Expect(hasArg).To(BeFalse(), "invalid arg must not be present on controller container") + }, time.Minute, 5*time.Second).Should(Succeed()) + }) + + It("should mark ExternalSecretsConfig Degraded for positional OPERAND_WEBHOOK_ARGS", func() { + By("Restoring a valid controller override so webhook invalidation is the only failure") + Expect(setOperatorManagerEnv(ctx, clientset, runtimeClient, map[string]string{ + externalsecrets.OperandExternalSecretsArgsEnvVar: controllerArg, + externalsecrets.OperandWebhookArgsEnvVar: "webhook,--port=10251", + })).To(Succeed()) + Expect(utils.VerifyPodsReadyByPrefix(ctx, clientset, operatorNamespace, []string{operatorPodPrefix})).To(Succeed()) + + By("Waiting for ExternalSecretsConfig to become Degraded") + Eventually(func(g Gomega) { + g.Expect(isExternalSecretsConfigDegraded(ctx)).To(BeTrue(), + "ExternalSecretsConfig should be Degraded for positional webhook args") + msg := externalSecretsConfigDegradedMessage(ctx) + g.Expect(msg).To(ContainSubstring("invalid custom arg override")) + g.Expect(msg).To(ContainSubstring(externalsecrets.OperandWebhookArgsEnvVar)) + }, 3*time.Minute, 5*time.Second).Should(Succeed()) + }) + + It("should recover from Degraded when invalid OPERAND_*_ARGS are corrected", func() { + By("Ensuring ExternalSecretsConfig is currently Degraded from prior invalid args") + Eventually(func(g Gomega) { + g.Expect(isExternalSecretsConfigDegraded(ctx)).To(BeTrue()) + }, time.Minute, 5*time.Second).Should(Succeed()) + + By("Correcting OPERAND_*_ARGS back to valid overrides") + Expect(setOperatorManagerEnv(ctx, clientset, runtimeClient, operandArgsEnv)).To(Succeed()) + Expect(utils.VerifyPodsReadyByPrefix(ctx, clientset, operatorNamespace, []string{operatorPodPrefix})).To(Succeed()) + + By("Waiting for ExternalSecretsConfig to become Ready again") + Expect(utils.WaitForExternalSecretsConfigReady(ctx, dynamicClient, common.ExternalSecretsConfigObjectName, 3*time.Minute)).To(Succeed()) + + By("Verifying valid overrides are applied after recovery") + waitForOperandArg(externalsecrets.OperandCoreControllerDeployment, externalsecrets.OperandCoreControllerContainer, controllerArg, true) + waitForOperandArg(externalsecrets.OperandWebhookDeployment, externalsecrets.OperandWebhookContainer, webhookArg, true) + }) + + It("should restore default operand args when OPERAND_*_ARGS are cleared", func() { + By("Clearing OPERAND_*_ARGS to verify restoration") + Expect(unsetOperatorManagerEnv(ctx, clientset, runtimeClient, operandArgsEnvKeys)).To(Succeed()) + Expect(utils.VerifyPodsReadyByPrefix(ctx, clientset, operatorNamespace, []string{operatorPodPrefix})).To(Succeed()) + + waitForOperandArg(externalsecrets.OperandCoreControllerDeployment, externalsecrets.OperandCoreControllerContainer, controllerArg, false) + Eventually(func(g Gomega) { + deployment, err := clientset.AppsV1().Deployments(operandNamespace).Get(ctx, externalsecrets.OperandCoreControllerDeployment, metav1.GetOptions{}) + g.Expect(err).NotTo(HaveOccurred()) + hasDefault, found := deploymentContainerHasArg(deployment, externalsecrets.OperandCoreControllerContainer, "--concurrent=1") + g.Expect(found).To(BeTrue()) + g.Expect(hasDefault).To(BeTrue(), "default --concurrent=1 should be restored") + }, 3*time.Minute, 5*time.Second).Should(Succeed()) + + waitForOperandArg(externalsecrets.OperandWebhookDeployment, externalsecrets.OperandWebhookContainer, webhookArg, false) + waitForOperandArg(externalsecrets.OperandBitwardenSDKServerDeployment, externalsecrets.OperandBitwardenContainer, bitwardenArg, false) + + esc := &operatorv1alpha1.ExternalSecretsConfig{} + Expect(runtimeClient.Get(ctx, client.ObjectKey{Name: common.ExternalSecretsConfigObjectName}, esc)).To(Succeed()) + if utils.IsCertControllerExpected(esc) { + waitForOperandArg(externalsecrets.OperandCertControllerDeployment, externalsecrets.OperandCertControllerContainer, certControllerArg, false) + } + }) +}) + +// externalSecretsConfigDegradedMessage returns the Degraded condition message, or "". +func externalSecretsConfigDegradedMessage(ctx context.Context) string { + esc := &operatorv1alpha1.ExternalSecretsConfig{} + if err := suiteRuntimeClient.Get(ctx, client.ObjectKey{Name: common.ExternalSecretsConfigObjectName}, esc); err != nil { + return "" + } + for _, cond := range esc.Status.Conditions { + if cond.Type == operatorv1alpha1.Degraded && cond.Status == metav1.ConditionTrue { + return cond.Message + } + } + return "" +} diff --git a/test/e2e/trusted_ca_bundle_test.go b/test/e2e/trusted_ca_bundle_test.go index cc5817c66..92bbbef09 100644 --- a/test/e2e/trusted_ca_bundle_test.go +++ b/test/e2e/trusted_ca_bundle_test.go @@ -418,20 +418,12 @@ func setTrustedCABundle(ctx context.Context, cmName, key string) { } func isExternalSecretsConfigDegraded(ctx context.Context) bool { - u, err := suiteDynamicClient.Resource(operatorv1alpha1.ExternalSecretsConfigGVR).Get(ctx, common.ExternalSecretsConfigObjectName, metav1.GetOptions{}) - if err != nil { - return false - } - conds, found, _ := unstructured.NestedSlice(u.Object, "status", "conditions") - if !found { + esc := &operatorv1alpha1.ExternalSecretsConfig{} + if err := suiteRuntimeClient.Get(ctx, client.ObjectKey{Name: common.ExternalSecretsConfigObjectName}, esc); err != nil { return false } - for _, c := range conds { - cond, ok := c.(map[string]interface{}) - if !ok { - continue - } - if cond["type"] == "Degraded" && cond["status"] == "True" { + for _, cond := range esc.Status.Conditions { + if cond.Type == operatorv1alpha1.Degraded && cond.Status == metav1.ConditionTrue { return true } } diff --git a/test/go.mod b/test/go.mod index 71eb0e4f3..7ed123b32 100644 --- a/test/go.mod +++ b/test/go.mod @@ -9,10 +9,11 @@ require ( github.com/onsi/ginkgo/v2 v2.27.4 github.com/onsi/gomega v1.39.0 github.com/openshift/external-secrets-operator v0.0.0-00010101000000-000000000000 + github.com/operator-framework/api v0.42.0 github.com/stretchr/testify v1.11.1 github.com/vmware-archive/yaml-patch v0.0.11 k8s.io/api v0.35.6 - k8s.io/apiextensions-apiserver v0.35.0 + k8s.io/apiextensions-apiserver v0.35.3 k8s.io/apimachinery v0.35.6 k8s.io/client-go v0.35.6 sigs.k8s.io/controller-runtime v0.23.3 @@ -32,32 +33,30 @@ require ( github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.22.0 // indirect - github.com/go-openapi/jsonreference v0.21.1 // indirect - github.com/go-openapi/swag v0.24.1 // indirect - github.com/go-openapi/swag/cmdutils v0.24.0 // indirect - github.com/go-openapi/swag/conv v0.24.0 // indirect - github.com/go-openapi/swag/fileutils v0.24.0 // indirect - github.com/go-openapi/swag/jsonname v0.24.0 // indirect - github.com/go-openapi/swag/jsonutils v0.24.0 // indirect - github.com/go-openapi/swag/loading v0.24.0 // indirect - github.com/go-openapi/swag/mangling v0.24.0 // indirect - github.com/go-openapi/swag/netutils v0.24.0 // indirect - github.com/go-openapi/swag/stringutils v0.24.0 // indirect - github.com/go-openapi/swag/typeutils v0.24.0 // indirect - github.com/go-openapi/swag/yamlutils v0.24.0 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 // indirect - github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/krishicks/yaml-patch v0.0.10 // indirect - github.com/mailru/easyjson v0.9.0 // indirect github.com/moby/spdystream v0.5.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect @@ -71,6 +70,7 @@ require ( github.com/prometheus/common v0.67.5 // indirect github.com/prometheus/procfs v0.19.2 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/otel v1.41.0 // indirect diff --git a/test/go.sum b/test/go.sum index 394bde4f1..3ccf4873d 100644 --- a/test/go.sum +++ b/test/go.sum @@ -42,34 +42,40 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.22.0 h1:TmMhghgNef9YXxTu1tOopo+0BGEytxA+okbry0HjZsM= -github.com/go-openapi/jsonpointer v0.22.0/go.mod h1:xt3jV88UtExdIkkL7NloURjRQjbeUgcxFblMjq2iaiU= -github.com/go-openapi/jsonreference v0.21.1 h1:bSKrcl8819zKiOgxkbVNRUBIr6Wwj9KYrDbMjRs0cDA= -github.com/go-openapi/jsonreference v0.21.1/go.mod h1:PWs8rO4xxTUqKGu+lEvvCxD5k2X7QYkKAepJyCmSTT8= -github.com/go-openapi/swag v0.24.1 h1:DPdYTZKo6AQCRqzwr/kGkxJzHhpKxZ9i/oX0zag+MF8= -github.com/go-openapi/swag v0.24.1/go.mod h1:sm8I3lCPlspsBBwUm1t5oZeWZS0s7m/A+Psg0ooRU0A= -github.com/go-openapi/swag/cmdutils v0.24.0 h1:KlRCffHwXFI6E5MV9n8o8zBRElpY4uK4yWyAMWETo9I= -github.com/go-openapi/swag/cmdutils v0.24.0/go.mod h1:uxib2FAeQMByyHomTlsP8h1TtPd54Msu2ZDU/H5Vuf8= -github.com/go-openapi/swag/conv v0.24.0 h1:ejB9+7yogkWly6pnruRX45D1/6J+ZxRu92YFivx54ik= -github.com/go-openapi/swag/conv v0.24.0/go.mod h1:jbn140mZd7EW2g8a8Y5bwm8/Wy1slLySQQ0ND6DPc2c= -github.com/go-openapi/swag/fileutils v0.24.0 h1:U9pCpqp4RUytnD689Ek/N1d2N/a//XCeqoH508H5oak= -github.com/go-openapi/swag/fileutils v0.24.0/go.mod h1:3SCrCSBHyP1/N+3oErQ1gP+OX1GV2QYFSnrTbzwli90= -github.com/go-openapi/swag/jsonname v0.24.0 h1:2wKS9bgRV/xB8c62Qg16w4AUiIrqqiniJFtZGi3dg5k= -github.com/go-openapi/swag/jsonname v0.24.0/go.mod h1:GXqrPzGJe611P7LG4QB9JKPtUZ7flE4DOVechNaDd7Q= -github.com/go-openapi/swag/jsonutils v0.24.0 h1:F1vE1q4pg1xtO3HTyJYRmEuJ4jmIp2iZ30bzW5XgZts= -github.com/go-openapi/swag/jsonutils v0.24.0/go.mod h1:vBowZtF5Z4DDApIoxcIVfR8v0l9oq5PpYRUuteVu6f0= -github.com/go-openapi/swag/loading v0.24.0 h1:ln/fWTwJp2Zkj5DdaX4JPiddFC5CHQpvaBKycOlceYc= -github.com/go-openapi/swag/loading v0.24.0/go.mod h1:gShCN4woKZYIxPxbfbyHgjXAhO61m88tmjy0lp/LkJk= -github.com/go-openapi/swag/mangling v0.24.0 h1:PGOQpViCOUroIeak/Uj/sjGAq9LADS3mOyjznmHy2pk= -github.com/go-openapi/swag/mangling v0.24.0/go.mod h1:Jm5Go9LHkycsz0wfoaBDkdc4CkpuSnIEf62brzyCbhc= -github.com/go-openapi/swag/netutils v0.24.0 h1:Bz02HRjYv8046Ycg/w80q3g9QCWeIqTvlyOjQPDjD8w= -github.com/go-openapi/swag/netutils v0.24.0/go.mod h1:WRgiHcYTnx+IqfMCtu0hy9oOaPR0HnPbmArSRN1SkZM= -github.com/go-openapi/swag/stringutils v0.24.0 h1:i4Z/Jawf9EvXOLUbT97O0HbPUja18VdBxeadyAqS1FM= -github.com/go-openapi/swag/stringutils v0.24.0/go.mod h1:5nUXB4xA0kw2df5PRipZDslPJgJut+NjL7D25zPZ/4w= -github.com/go-openapi/swag/typeutils v0.24.0 h1:d3szEGzGDf4L2y1gYOSSLeK6h46F+zibnEas2Jm/wIw= -github.com/go-openapi/swag/typeutils v0.24.0/go.mod h1:q8C3Kmk/vh2VhpCLaoR2MVWOGP8y7Jc8l82qCTd1DYI= -github.com/go-openapi/swag/yamlutils v0.24.0 h1:bhw4894A7Iw6ne+639hsBNRHg9iZg/ISrOVr+sJGp4c= -github.com/go-openapi/swag/yamlutils v0.24.0/go.mod h1:DpKv5aYuaGm/sULePoeiG8uwMpZSfReo1HR3Ik0yaG8= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= @@ -84,8 +90,8 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 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/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -105,8 +111,6 @@ github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 h1:liMMTbpW github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -121,8 +125,6 @@ github.com/krishicks/yaml-patch v0.0.10 h1:H4FcHpnNwVmw8u0MjPRjWyIXtco6zM2F78t+5 github.com/krishicks/yaml-patch v0.0.10/go.mod h1:Sm5TchwZS6sm7RJoyg87tzxm2ZcKzdRE4Q7TjNhPrME= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= @@ -154,6 +156,8 @@ github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q= github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/operator-framework/api v0.42.0 h1:rkc5V3zW8RxZMjePAe12jdL7Co/hwsYo1pLnkkhuR7s= +github.com/operator-framework/api v0.42.0/go.mod h1:bMEj+wl/8tGqcGNtxt38cLUYagu9chNsbYzb/5HQaUQ= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -171,6 +175,8 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -285,8 +291,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.35.6 h1:phPzP79F3kcONsD2TzmDiITNCV6/1Z5U3CCEcjtsXzI= k8s.io/api v0.35.6/go.mod h1:GWKUaIp24fuDFigAgnhr9EJOKDqspnwPjYlpDca5B4U= -k8s.io/apiextensions-apiserver v0.35.0 h1:3xHk2rTOdWXXJM+RDQZJvdx0yEOgC0FgQ1PlJatA5T4= -k8s.io/apiextensions-apiserver v0.35.0/go.mod h1:E1Ahk9SADaLQ4qtzYFkwUqusXTcaV2uw3l14aqpL2LU= +k8s.io/apiextensions-apiserver v0.35.3 h1:2fQUhEO7P17sijylbdwt0nBdXP0TvHrHj0KeqHD8FiU= +k8s.io/apiextensions-apiserver v0.35.3/go.mod h1:tK4Kz58ykRpwAEkXUb634HD1ZAegEElktz/B3jgETd8= k8s.io/apimachinery v0.35.6 h1:ASSpfmmsOArKb2Hsu8gGlIcbIcEMVTboI3FfsfYuQ8k= k8s.io/apimachinery v0.35.6/go.mod h1:NNi1taPOpep0jOj+oRha3mBJPqvi0hGdaV8TCqGQ+cc= k8s.io/apiserver v0.35.6 h1:VWYg2S0wlAmN3URFpVeuLa4PP2RCpTFg1nvlUHOy2C8= diff --git a/tools/go.mod b/tools/go.mod index 4697cea85..bae378a74 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -19,7 +19,6 @@ require ( require ( 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect 4d63.com/gochecknoglobals v0.2.2 // indirect - cel.dev/expr v0.25.1 // indirect codeberg.org/chavacava/garif v0.2.0 // indirect codeberg.org/polyfloyd/go-errorlint v1.9.0 // indirect dev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect @@ -46,7 +45,6 @@ require ( github.com/alfatraining/structtag v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect github.com/alingse/nilnesserr v0.2.0 // indirect - github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/ashanbrown/forbidigo/v2 v2.3.0 // indirect github.com/ashanbrown/makezero/v2 v2.1.0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect @@ -89,20 +87,20 @@ require ( github.com/go-errors/errors v1.4.2 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.22.0 // indirect - github.com/go-openapi/jsonreference v0.21.1 // indirect - github.com/go-openapi/swag v0.24.1 // indirect - github.com/go-openapi/swag/cmdutils v0.24.0 // indirect - github.com/go-openapi/swag/conv v0.24.0 // indirect - github.com/go-openapi/swag/fileutils v0.24.0 // indirect - github.com/go-openapi/swag/jsonname v0.24.0 // indirect - github.com/go-openapi/swag/jsonutils v0.24.0 // indirect - github.com/go-openapi/swag/loading v0.24.0 // indirect - github.com/go-openapi/swag/mangling v0.24.0 // indirect - github.com/go-openapi/swag/netutils v0.24.0 // indirect - github.com/go-openapi/swag/stringutils v0.24.0 // indirect - github.com/go-openapi/swag/typeutils v0.24.0 // indirect - github.com/go-openapi/swag/yamlutils v0.24.0 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect @@ -128,7 +126,8 @@ require ( github.com/golangci/revgrep v0.8.0 // indirect github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e // indirect github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/cel-go v0.31.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect github.com/google/uuid v1.6.0 // indirect @@ -148,7 +147,6 @@ require ( github.com/jgautheron/goconst v1.8.2 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect github.com/jjti/go-spancheck v0.6.5 // indirect - github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/julz/importas v0.2.0 // indirect github.com/karamaru-alpha/copyloopvar v1.2.2 // indirect @@ -167,7 +165,6 @@ require ( github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/macabu/inamedparam v0.2.0 // indirect github.com/magiconair/properties v1.8.6 // indirect - github.com/mailru/easyjson v0.9.0 // indirect github.com/manuelarte/embeddedstructfieldcheck v0.4.0 // indirect github.com/manuelarte/funcorder v0.5.0 // indirect github.com/maratori/testableexamples v1.0.1 // indirect @@ -215,7 +212,7 @@ require ( github.com/sashamelentyev/usestdlibvars v1.29.0 // indirect github.com/securego/gosec/v2 v2.22.11 // indirect github.com/sergi/go-diff v1.2.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect github.com/sivchari/containedctx v1.0.3 // indirect github.com/sonatard/noctx v0.4.0 // indirect github.com/sourcegraph/go-diff v0.7.0 // indirect @@ -260,7 +257,7 @@ require ( go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.47.0 // indirect - golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect + golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect golang.org/x/exp/typeparams v0.0.0-20251023183803-a4bb9ffd2546 // indirect golang.org/x/mod v0.32.0 // indirect golang.org/x/net v0.49.0 // indirect @@ -270,8 +267,8 @@ require ( golang.org/x/text v0.33.0 // indirect golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.41.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260202165425-ce8ad4cf556b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260202165425-ce8ad4cf556b // indirect google.golang.org/grpc v1.79.3 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect @@ -281,10 +278,10 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect honnef.co/go/tools v0.6.1 // indirect k8s.io/api v0.35.6 // indirect - k8s.io/apiextensions-apiserver v0.35.0 // indirect + k8s.io/apiextensions-apiserver v0.35.3 // indirect k8s.io/apimachinery v0.35.6 // indirect k8s.io/apiserver v0.35.6 // indirect - k8s.io/code-generator v0.35.0 // indirect + k8s.io/code-generator v0.35.3 // indirect k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect k8s.io/klog/v2 v2.140.0 // indirect k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect diff --git a/tools/go.sum b/tools/go.sum index aa048c217..73f9be06f 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -168,34 +168,40 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.22.0 h1:TmMhghgNef9YXxTu1tOopo+0BGEytxA+okbry0HjZsM= -github.com/go-openapi/jsonpointer v0.22.0/go.mod h1:xt3jV88UtExdIkkL7NloURjRQjbeUgcxFblMjq2iaiU= -github.com/go-openapi/jsonreference v0.21.1 h1:bSKrcl8819zKiOgxkbVNRUBIr6Wwj9KYrDbMjRs0cDA= -github.com/go-openapi/jsonreference v0.21.1/go.mod h1:PWs8rO4xxTUqKGu+lEvvCxD5k2X7QYkKAepJyCmSTT8= -github.com/go-openapi/swag v0.24.1 h1:DPdYTZKo6AQCRqzwr/kGkxJzHhpKxZ9i/oX0zag+MF8= -github.com/go-openapi/swag v0.24.1/go.mod h1:sm8I3lCPlspsBBwUm1t5oZeWZS0s7m/A+Psg0ooRU0A= -github.com/go-openapi/swag/cmdutils v0.24.0 h1:KlRCffHwXFI6E5MV9n8o8zBRElpY4uK4yWyAMWETo9I= -github.com/go-openapi/swag/cmdutils v0.24.0/go.mod h1:uxib2FAeQMByyHomTlsP8h1TtPd54Msu2ZDU/H5Vuf8= -github.com/go-openapi/swag/conv v0.24.0 h1:ejB9+7yogkWly6pnruRX45D1/6J+ZxRu92YFivx54ik= -github.com/go-openapi/swag/conv v0.24.0/go.mod h1:jbn140mZd7EW2g8a8Y5bwm8/Wy1slLySQQ0ND6DPc2c= -github.com/go-openapi/swag/fileutils v0.24.0 h1:U9pCpqp4RUytnD689Ek/N1d2N/a//XCeqoH508H5oak= -github.com/go-openapi/swag/fileutils v0.24.0/go.mod h1:3SCrCSBHyP1/N+3oErQ1gP+OX1GV2QYFSnrTbzwli90= -github.com/go-openapi/swag/jsonname v0.24.0 h1:2wKS9bgRV/xB8c62Qg16w4AUiIrqqiniJFtZGi3dg5k= -github.com/go-openapi/swag/jsonname v0.24.0/go.mod h1:GXqrPzGJe611P7LG4QB9JKPtUZ7flE4DOVechNaDd7Q= -github.com/go-openapi/swag/jsonutils v0.24.0 h1:F1vE1q4pg1xtO3HTyJYRmEuJ4jmIp2iZ30bzW5XgZts= -github.com/go-openapi/swag/jsonutils v0.24.0/go.mod h1:vBowZtF5Z4DDApIoxcIVfR8v0l9oq5PpYRUuteVu6f0= -github.com/go-openapi/swag/loading v0.24.0 h1:ln/fWTwJp2Zkj5DdaX4JPiddFC5CHQpvaBKycOlceYc= -github.com/go-openapi/swag/loading v0.24.0/go.mod h1:gShCN4woKZYIxPxbfbyHgjXAhO61m88tmjy0lp/LkJk= -github.com/go-openapi/swag/mangling v0.24.0 h1:PGOQpViCOUroIeak/Uj/sjGAq9LADS3mOyjznmHy2pk= -github.com/go-openapi/swag/mangling v0.24.0/go.mod h1:Jm5Go9LHkycsz0wfoaBDkdc4CkpuSnIEf62brzyCbhc= -github.com/go-openapi/swag/netutils v0.24.0 h1:Bz02HRjYv8046Ycg/w80q3g9QCWeIqTvlyOjQPDjD8w= -github.com/go-openapi/swag/netutils v0.24.0/go.mod h1:WRgiHcYTnx+IqfMCtu0hy9oOaPR0HnPbmArSRN1SkZM= -github.com/go-openapi/swag/stringutils v0.24.0 h1:i4Z/Jawf9EvXOLUbT97O0HbPUja18VdBxeadyAqS1FM= -github.com/go-openapi/swag/stringutils v0.24.0/go.mod h1:5nUXB4xA0kw2df5PRipZDslPJgJut+NjL7D25zPZ/4w= -github.com/go-openapi/swag/typeutils v0.24.0 h1:d3szEGzGDf4L2y1gYOSSLeK6h46F+zibnEas2Jm/wIw= -github.com/go-openapi/swag/typeutils v0.24.0/go.mod h1:q8C3Kmk/vh2VhpCLaoR2MVWOGP8y7Jc8l82qCTd1DYI= -github.com/go-openapi/swag/yamlutils v0.24.0 h1:bhw4894A7Iw6ne+639hsBNRHg9iZg/ISrOVr+sJGp4c= -github.com/go-openapi/swag/yamlutils v0.24.0/go.mod h1:DpKv5aYuaGm/sULePoeiG8uwMpZSfReo1HR3Ik0yaG8= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= @@ -255,10 +261,10 @@ github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e h1:ai0EfmVYE2b github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e/go.mod h1:Vrn4B5oR9qRwM+f54koyeH3yzphlecwERs0el27Fr/s= github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqtt0ssnqSJNNndxe69DOQ24A5h7+i3KpM= github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e/go.mod h1:h+wZwLjUTJnm/P2rwlbJdRPZXOzaT36/FwnPnY2inzc= -github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI= -github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/cel-go v0.31.0 h1:H0bhpFTqOvmHrBGrWKp7ZlhBm5Hh8PYUEXnwxT1LL7A= +github.com/google/cel-go v0.31.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU= github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -315,8 +321,6 @@ github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjz github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8= github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -362,8 +366,6 @@ github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddB github.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/manuelarte/embeddedstructfieldcheck v0.4.0 h1:3mAIyaGRtjK6EO9E73JlXLtiy7ha80b2ZVGyacxgfww= github.com/manuelarte/embeddedstructfieldcheck v0.4.0/go.mod h1:z8dFSyXqp+fC6NLDSljRJeNQJJDWnY7RoWFzV3PC6UM= github.com/manuelarte/funcorder v0.5.0 h1:llMuHXXbg7tD0i/LNw8vGnkDTHFpTnWqKPI85Rknc+8= @@ -494,8 +496,8 @@ github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= github.com/sonatard/noctx v0.4.0 h1:7MC/5Gg4SQ4lhLYR6mvOP6mQVSxCrdyiExo7atBs27o= @@ -520,8 +522,6 @@ github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YE github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/stbenjam/no-sprintf-host-port v0.3.1 h1:AyX7+dxI4IdLBPtDbsGAyqiTSLpCP9hWRrXQDU4Cm/g= github.com/stbenjam/no-sprintf-host-port v0.3.1/go.mod h1:ODbZesTCHMVKthBHskvUUexdcNHAQRXk9NpSsL8p/HQ= -github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= -github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -638,8 +638,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= -golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0= -golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20251023183803-a4bb9ffd2546 h1:HDjDiATsGqvuqvkDvgJjD1IgPrVekcSXVVE21JwvzGE= @@ -696,7 +696,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -751,10 +750,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/api v0.0.0-20260202165425-ce8ad4cf556b h1:SGYyueaEovpqmWmtTvwtVgo638V/QFE2zlTCnRrR3jg= +google.golang.org/genproto/googleapis/api v0.0.0-20260202165425-ce8ad4cf556b/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260202165425-ce8ad4cf556b h1:GZxXGdFaHX27ZSMHudWc4FokdD+xl8BC2UJm1OVIEzs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260202165425-ce8ad4cf556b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= @@ -782,16 +781,16 @@ honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= k8s.io/api v0.35.6 h1:phPzP79F3kcONsD2TzmDiITNCV6/1Z5U3CCEcjtsXzI= k8s.io/api v0.35.6/go.mod h1:GWKUaIp24fuDFigAgnhr9EJOKDqspnwPjYlpDca5B4U= -k8s.io/apiextensions-apiserver v0.35.0 h1:3xHk2rTOdWXXJM+RDQZJvdx0yEOgC0FgQ1PlJatA5T4= -k8s.io/apiextensions-apiserver v0.35.0/go.mod h1:E1Ahk9SADaLQ4qtzYFkwUqusXTcaV2uw3l14aqpL2LU= +k8s.io/apiextensions-apiserver v0.35.3 h1:2fQUhEO7P17sijylbdwt0nBdXP0TvHrHj0KeqHD8FiU= +k8s.io/apiextensions-apiserver v0.35.3/go.mod h1:tK4Kz58ykRpwAEkXUb634HD1ZAegEElktz/B3jgETd8= k8s.io/apimachinery v0.35.6 h1:ASSpfmmsOArKb2Hsu8gGlIcbIcEMVTboI3FfsfYuQ8k= k8s.io/apimachinery v0.35.6/go.mod h1:NNi1taPOpep0jOj+oRha3mBJPqvi0hGdaV8TCqGQ+cc= k8s.io/apiserver v0.35.6 h1:VWYg2S0wlAmN3URFpVeuLa4PP2RCpTFg1nvlUHOy2C8= k8s.io/apiserver v0.35.6/go.mod h1:wajGSrXO9w+lx69jYq4SaE4Xxw5KxxwvVD1zbttYA2E= k8s.io/client-go v0.35.6 h1:qZQv9a5B4YlIpXhFBwsI9qPOOJC6Z8lk9lkEWmrmus8= k8s.io/client-go v0.35.6/go.mod h1:LOO6N1EhxdQAzYIZ/73cJVyb3gixrMY6ZDJcJ/ANfsY= -k8s.io/code-generator v0.35.0 h1:TvrtfKYZTm9oDF2z+veFKSCcgZE3Igv0svY+ehCmjHQ= -k8s.io/code-generator v0.35.0/go.mod h1:iS1gvVf3c/T71N5DOGYO+Gt3PdJ6B9LYSvIyQ4FHzgc= +k8s.io/code-generator v0.35.3 h1:NDGCLkEm6Ho65wTdSe2EgErmmtsrezOPwwOchlNc6FQ= +k8s.io/code-generator v0.35.3/go.mod h1:LAVriRGXQusHQ0Ns64SE1ublSswm1KrK7cXn0GuQETg= k8s.io/component-base v0.35.6 h1:dTkck9uefkIrKn7wRCEYiDWNUvHd8UdwZCcVafmHgL4= k8s.io/component-base v0.35.6/go.mod h1:qcNKrspACsqR+vgUJXkWzwtgUGkURcnrus41o92jjpk= k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b h1:gMplByicHV/TJBizHd9aVEsTYoJBnnUAT5MHlTkbjhQ= diff --git a/vendor/github.com/go-openapi/jsonpointer/.cliff.toml b/vendor/github.com/go-openapi/jsonpointer/.cliff.toml new file mode 100644 index 000000000..702629f5d --- /dev/null +++ b/vendor/github.com/go-openapi/jsonpointer/.cliff.toml @@ -0,0 +1,181 @@ +# git-cliff ~ configuration file +# https://git-cliff.org/docs/configuration + +[changelog] +header = """ +""" + +footer = """ + +----- + +**[{{ remote.github.repo }}]({{ self::remote_url() }}) license terms** + +[![License][license-badge]][license-url] + +[license-badge]: http://img.shields.io/badge/license-Apache%20v2-orange.svg +[license-url]: {{ self::remote_url() }}/?tab=Apache-2.0-1-ov-file#readme + +{%- macro remote_url() -%} + https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} +{%- endmacro -%} +""" + +body = """ +{%- if version %} +## [{{ version | trim_start_matches(pat="v") }}]({{ self::remote_url() }}/tree/{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }} +{%- else %} +## [unreleased] +{%- endif %} +{%- if message %} + {%- raw %}\n{% endraw %} +{{ message }} + {%- raw %}\n{% endraw %} +{%- endif %} +{%- if version %} + {%- if previous.version %} + +**Full Changelog**: <{{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }}> + {%- endif %} +{%- else %} + {%- raw %}\n{% endraw %} +{%- endif %} + +{%- if statistics %}{% if statistics.commit_count %} + {%- raw %}\n{% endraw %} +{{ statistics.commit_count }} commits in this release. + {%- raw %}\n{% endraw %} +{%- endif %}{% endif %} +----- + +{%- for group, commits in commits | group_by(attribute="group") %} + {%- raw %}\n{% endraw %} +### {{ group | upper_first }} + {%- raw %}\n{% endraw %} + {%- for commit in commits %} + {%- if commit.remote.pr_title %} + {%- set commit_message = commit.remote.pr_title %} + {%- else %} + {%- set commit_message = commit.message %} + {%- endif %} +* {{ commit_message | split(pat="\n") | first | trim }} + {%- if commit.remote.username %} +{%- raw %} {% endraw %}by [@{{ commit.remote.username }}](https://github.com/{{ commit.remote.username }}) + {%- endif %} + {%- if commit.remote.pr_number %} +{%- raw %} {% endraw %}in [#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }}) + {%- endif %} +{%- raw %} {% endraw %}[...]({{ self::remote_url() }}/commit/{{ commit.id }}) + {%- endfor %} +{%- endfor %} + +{%- if github %} +{%- raw %}\n{% endraw -%} + {%- set all_contributors = github.contributors | length %} + {%- if github.contributors | filter(attribute="username", value="dependabot[bot]") | length < all_contributors %} +----- + +### People who contributed to this release + {% endif %} + {%- for contributor in github.contributors | filter(attribute="username") | sort(attribute="username") %} + {%- if contributor.username != "dependabot[bot]" and contributor.username != "github-actions[bot]" %} +* [@{{ contributor.username }}](https://github.com/{{ contributor.username }}) + {%- endif %} + {%- endfor %} + + {% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %} +----- + {%- raw %}\n{% endraw %} + +### New Contributors + {%- endif %} + + {%- for contributor in github.contributors | filter(attribute="is_first_time", value=true) %} + {%- if contributor.username != "dependabot[bot]" and contributor.username != "github-actions[bot]" %} +* @{{ contributor.username }} made their first contribution + {%- if contributor.pr_number %} + in [#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \ + {%- endif %} + {%- endif %} + {%- endfor %} +{%- endif %} + +{%- raw %}\n{% endraw %} + +{%- macro remote_url() -%} + https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} +{%- endmacro -%} +""" +# Remove leading and trailing whitespaces from the changelog's body. +trim = true +# Render body even when there are no releases to process. +render_always = true +# An array of regex based postprocessors to modify the changelog. +postprocessors = [ + # Replace the placeholder with a URL. + #{ pattern = '', replace = "https://github.com/orhun/git-cliff" }, +] +# output file path +# output = "test.md" + +[git] +# Parse commits according to the conventional commits specification. +# See https://www.conventionalcommits.org +conventional_commits = false +# Exclude commits that do not match the conventional commits specification. +filter_unconventional = false +# Require all commits to be conventional. +# Takes precedence over filter_unconventional. +require_conventional = false +# Split commits on newlines, treating each line as an individual commit. +split_commits = false +# An array of regex based parsers to modify commit messages prior to further processing. +commit_preprocessors = [ + # Replace issue numbers with link templates to be updated in `changelog.postprocessors`. + #{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))"}, + # Check spelling of the commit message using https://github.com/crate-ci/typos. + # If the spelling is incorrect, it will be fixed automatically. + #{ pattern = '.*', replace_command = 'typos --write-changes -' } +] +# Prevent commits that are breaking from being excluded by commit parsers. +protect_breaking_commits = false +# An array of regex based parsers for extracting data from the commit message. +# Assigns commits to groups. +# Optionally sets the commit's scope and can decide to exclude commits from further processing. +commit_parsers = [ + { message = "^[Cc]hore\\([Rr]elease\\): prepare for", skip = true }, + { message = "(^[Mm]erge)|([Mm]erge conflict)", skip = true }, + { field = "author.name", pattern = "dependabot*", group = "Updates" }, + { message = "([Ss]ecurity)|([Vv]uln)", group = "Security" }, + { body = "(.*[Ss]ecurity)|([Vv]uln)", group = "Security" }, + { message = "([Cc]hore\\(lint\\))|(style)|(lint)|(codeql)|(golangci)", group = "Code quality" }, + { message = "(^[Dd]oc)|((?i)readme)|(badge)|(typo)|(documentation)", group = "Documentation" }, + { message = "(^[Ff]eat)|(^[Ee]nhancement)", group = "Implemented enhancements" }, + { message = "(^ci)|(\\(ci\\))|(fixup\\s+ci)|(fix\\s+ci)|(license)|(example)", group = "Miscellaneous tasks" }, + { message = "^test", group = "Testing" }, + { message = "(^fix)|(panic)", group = "Fixed bugs" }, + { message = "(^refact)|(rework)", group = "Refactor" }, + { message = "(^[Pp]erf)|(performance)", group = "Performance" }, + { message = "(^[Cc]hore)", group = "Miscellaneous tasks" }, + { message = "^[Rr]evert", group = "Reverted changes" }, + { message = "(upgrade.*?go)|(go\\s+version)", group = "Updates" }, + { message = ".*", group = "Other" }, +] +# Exclude commits that are not matched by any commit parser. +filter_commits = false +# An array of link parsers for extracting external references, and turning them into URLs, using regex. +link_parsers = [] +# Include only the tags that belong to the current branch. +use_branch_tags = false +# Order releases topologically instead of chronologically. +topo_order = false +# Order releases topologically instead of chronologically. +topo_order_commits = true +# Order of commits in each group/release within the changelog. +# Allowed values: newest, oldest +sort_commits = "newest" +# Process submodules commits +recurse_submodules = false + +#[remote.github] +#owner = "go-openapi" diff --git a/vendor/github.com/go-openapi/jsonpointer/.gitignore b/vendor/github.com/go-openapi/jsonpointer/.gitignore index 769c24400..59cd29489 100644 --- a/vendor/github.com/go-openapi/jsonpointer/.gitignore +++ b/vendor/github.com/go-openapi/jsonpointer/.gitignore @@ -1 +1,4 @@ -secrets.yml +*.out +*.cov +.idea +.env diff --git a/vendor/github.com/go-openapi/jsonpointer/.golangci.yml b/vendor/github.com/go-openapi/jsonpointer/.golangci.yml index 568ce16d7..fdae591bc 100644 --- a/vendor/github.com/go-openapi/jsonpointer/.golangci.yml +++ b/vendor/github.com/go-openapi/jsonpointer/.golangci.yml @@ -2,34 +2,17 @@ version: "2" linters: default: all disable: - - cyclop - depguard - - errchkjson - - errorlint - - exhaustruct - - forcetypeassert - funlen - - gochecknoglobals - - gochecknoinits - - gocognit - - godot - godox - - gosmopolitan - - inamedparam - - intrange # disabled while < go1.22 - - ireturn - - lll - - musttag - - nestif + - exhaustruct - nlreturn - nonamedreturns - noinlineerr - paralleltest - recvcheck - testpackage - - thelper - tparallel - - unparam - varnamelen - whitespace - wrapcheck @@ -41,8 +24,15 @@ linters: goconst: min-len: 2 min-occurrences: 3 + cyclop: + max-complexity: 20 gocyclo: - min-complexity: 45 + min-complexity: 20 + exhaustive: + default-signifies-exhaustive: true + default-case-required: true + lll: + line-length: 180 exclusions: generated: lax presets: @@ -58,6 +48,7 @@ formatters: enable: - gofmt - goimports + - gofumpt exclusions: generated: lax paths: diff --git a/vendor/github.com/go-openapi/jsonpointer/CONTRIBUTORS.md b/vendor/github.com/go-openapi/jsonpointer/CONTRIBUTORS.md new file mode 100644 index 000000000..03c098316 --- /dev/null +++ b/vendor/github.com/go-openapi/jsonpointer/CONTRIBUTORS.md @@ -0,0 +1,24 @@ +# Contributors + +- Repository: ['go-openapi/jsonpointer'] + +| Total Contributors | Total Contributions | +| --- | --- | +| 12 | 95 | + +| Username | All Time Contribution Count | All Commits | +| --- | --- | --- | +| @fredbi | 48 | https://github.com/go-openapi/jsonpointer/commits?author=fredbi | +| @casualjim | 33 | https://github.com/go-openapi/jsonpointer/commits?author=casualjim | +| @magodo | 3 | https://github.com/go-openapi/jsonpointer/commits?author=magodo | +| @youyuanwu | 3 | https://github.com/go-openapi/jsonpointer/commits?author=youyuanwu | +| @gaiaz-iusipov | 1 | https://github.com/go-openapi/jsonpointer/commits?author=gaiaz-iusipov | +| @gbjk | 1 | https://github.com/go-openapi/jsonpointer/commits?author=gbjk | +| @gordallott | 1 | https://github.com/go-openapi/jsonpointer/commits?author=gordallott | +| @ianlancetaylor | 1 | https://github.com/go-openapi/jsonpointer/commits?author=ianlancetaylor | +| @mfleader | 1 | https://github.com/go-openapi/jsonpointer/commits?author=mfleader | +| @Neo2308 | 1 | https://github.com/go-openapi/jsonpointer/commits?author=Neo2308 | +| @olivierlemasle | 1 | https://github.com/go-openapi/jsonpointer/commits?author=olivierlemasle | +| @testwill | 1 | https://github.com/go-openapi/jsonpointer/commits?author=testwill | + + _this file was generated by the [Contributors GitHub Action](https://github.com/github/contributors)_ diff --git a/vendor/github.com/go-openapi/jsonpointer/LICENSE b/vendor/github.com/go-openapi/jsonpointer/LICENSE index d64569567..261eeb9e9 100644 --- a/vendor/github.com/go-openapi/jsonpointer/LICENSE +++ b/vendor/github.com/go-openapi/jsonpointer/LICENSE @@ -1,4 +1,3 @@ - Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ diff --git a/vendor/github.com/go-openapi/jsonpointer/NOTICE b/vendor/github.com/go-openapi/jsonpointer/NOTICE new file mode 100644 index 000000000..f3b51939a --- /dev/null +++ b/vendor/github.com/go-openapi/jsonpointer/NOTICE @@ -0,0 +1,39 @@ +Copyright 2015-2025 go-swagger maintainers + +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +This software library, github.com/go-openapi/jsonpointer, includes software developed +by the go-swagger and go-openapi maintainers ("go-swagger maintainers"). + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this software except in compliance with the License. + +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0. + +This software is copied from, derived from, and inspired by other original software products. +It ships with copies of other software which license terms are recalled below. + +The original software was authored on 25-02-2013 by sigu-399 (https://github.com/sigu-399, sigu.399@gmail.com). + +github.com/sigh-399/jsonpointer +=========================== + +// SPDX-FileCopyrightText: Copyright 2013 sigu-399 ( https://github.com/sigu-399 ) +// SPDX-License-Identifier: Apache-2.0 + +Copyright 2013 sigu-399 ( https://github.com/sigu-399 ) + +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. diff --git a/vendor/github.com/go-openapi/jsonpointer/README.md b/vendor/github.com/go-openapi/jsonpointer/README.md index 0108f1d57..b61b63fd9 100644 --- a/vendor/github.com/go-openapi/jsonpointer/README.md +++ b/vendor/github.com/go-openapi/jsonpointer/README.md @@ -1,19 +1,149 @@ -# gojsonpointer [![Build Status](https://github.com/go-openapi/jsonpointer/actions/workflows/go-test.yml/badge.svg)](https://github.com/go-openapi/jsonpointer/actions?query=workflow%3A"go+test") [![codecov](https://codecov.io/gh/go-openapi/jsonpointer/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/jsonpointer) +# jsonpointer -[![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io) -[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/jsonpointer/master/LICENSE) -[![Go Reference](https://pkg.go.dev/badge/github.com/go-openapi/jsonpointer.svg)](https://pkg.go.dev/github.com/go-openapi/jsonpointer) -[![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/jsonpointer)](https://goreportcard.com/report/github.com/go-openapi/jsonpointer) + +[![Tests][test-badge]][test-url] [![Coverage][cov-badge]][cov-url] [![CI vuln scan][vuln-scan-badge]][vuln-scan-url] [![CodeQL][codeql-badge]][codeql-url] + + + +[![Release][release-badge]][release-url] [![Go Report Card][gocard-badge]][gocard-url] [![CodeFactor Grade][codefactor-badge]][codefactor-url] [![License][license-badge]][license-url] + + +[![GoDoc][godoc-badge]][godoc-url] [![Slack Channel][slack-logo]![slack-badge]][slack-url] [![go version][goversion-badge]][goversion-url] ![Top language][top-badge] ![Commits since latest release][commits-badge] -An implementation of JSON Pointer - Go language +--- + +An implementation of JSON Pointer for golang, which supports go `struct`. ## Status -Completed YES -Tested YES +API is stable. + +## Import this library in your project + +```cmd +go get github.com/go-openapi/jsonpointer +``` + +## Basic usage + +See also some [examples](./examples_test.go) + +### Retrieving a value + +```go + import ( + "github.com/go-openapi/jsonpointer" + ) + + + var doc any + + ... + + pointer, err := jsonpointer.New("/foo/1") + if err != nil { + ... // error: e.g. invalid JSON pointer specification + } + + value, kind, err := pointer.Get(doc) + if err != nil { + ... // error: e.g. key not found, index out of bounds, etc. + } + + ... +``` + +### Setting a value + +```go + ... + var doc any + ... + pointer, err := jsonpointer.New("/foo/1") + if err != nil { + ... // error: e.g. invalid JSON pointer specification + } + + doc, err = p.Set(doc, "value") + if err != nil { + ... // error: e.g. key not found, index out of bounds, etc. + } +``` + +## Change log + +See ## References -http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07 -### Note -The 4.Evaluation part of the previous reference, starting with 'If the currently referenced value is a JSON array, the reference token MUST contain either...' is not implemented. + + +also known as [RFC6901](https://www.rfc-editor.org/rfc/rfc6901) + +## Licensing + +This library ships under the [SPDX-License-Identifier: Apache-2.0](./LICENSE). + +See the license [NOTICE](./NOTICE), which recalls the licensing terms of all the pieces of software +on top of which it has been built. + +## Limitations + +The 4.Evaluation part of the previous reference, starting with 'If the currently referenced value is a JSON array, +the reference token MUST contain either...' is not implemented. + +That is because our implementation of the JSON pointer only supports explicit references to array elements: +the provision in the spec to resolve non-existent members as "the last element in the array", +using the special trailing character "-" is not implemented. + +## Other documentation + +* [All-time contributors](./CONTRIBUTORS.md) +* [Contributing guidelines](.github/CONTRIBUTING.md) +* [Maintainers documentation](docs/MAINTAINERS.md) +* [Code style](docs/STYLE.md) + +## Cutting a new release + +Maintainers can cut a new release by either: + +* running [this workflow](https://github.com/go-openapi/jsonpointer/actions/workflows/bump-release.yml) +* or pushing a semver tag + * signed tags are preferred + * The tag message is prepended to release notes + + +[test-badge]: https://github.com/go-openapi/jsonpointer/actions/workflows/go-test.yml/badge.svg +[test-url]: https://github.com/go-openapi/jsonpointer/actions/workflows/go-test.yml +[cov-badge]: https://codecov.io/gh/go-openapi/jsonpointer/branch/master/graph/badge.svg +[cov-url]: https://codecov.io/gh/go-openapi/jsonpointer +[vuln-scan-badge]: https://github.com/go-openapi/jsonpointer/actions/workflows/scanner.yml/badge.svg +[vuln-scan-url]: https://github.com/go-openapi/jsonpointer/actions/workflows/scanner.yml +[codeql-badge]: https://github.com/go-openapi/jsonpointer/actions/workflows/codeql.yml/badge.svg +[codeql-url]: https://github.com/go-openapi/jsonpointer/actions/workflows/codeql.yml + +[release-badge]: https://badge.fury.io/gh/go-openapi%2Fjsonpointer.svg +[release-url]: https://badge.fury.io/gh/go-openapi%2Fjsonpointer +[gomod-badge]: https://badge.fury.io/go/github.com%2Fgo-openapi%2Fjsonpointer.svg +[gomod-url]: https://badge.fury.io/go/github.com%2Fgo-openapi%2Fjsonpointer + +[gocard-badge]: https://goreportcard.com/badge/github.com/go-openapi/jsonpointer +[gocard-url]: https://goreportcard.com/report/github.com/go-openapi/jsonpointer +[codefactor-badge]: https://img.shields.io/codefactor/grade/github/go-openapi/jsonpointer +[codefactor-url]: https://www.codefactor.io/repository/github/go-openapi/jsonpointer + +[doc-badge]: https://img.shields.io/badge/doc-site-blue?link=https%3A%2F%2Fgoswagger.io%2Fgo-openapi%2F +[doc-url]: https://goswagger.io/go-openapi +[godoc-badge]: https://pkg.go.dev/badge/github.com/go-openapi/jsonpointer +[godoc-url]: http://pkg.go.dev/github.com/go-openapi/jsonpointer +[slack-logo]: https://a.slack-edge.com/e6a93c1/img/icons/favicon-32.png +[slack-badge]: https://img.shields.io/badge/slack-blue?link=https%3A%2F%2Fgoswagger.slack.com%2Farchives%2FC04R30YM +[slack-url]: https://goswagger.slack.com/archives/C04R30YMU + +[license-badge]: http://img.shields.io/badge/license-Apache%20v2-orange.svg +[license-url]: https://github.com/go-openapi/jsonpointer/?tab=Apache-2.0-1-ov-file#readme + +[goversion-badge]: https://img.shields.io/github/go-mod/go-version/go-openapi/jsonpointer +[goversion-url]: https://github.com/go-openapi/jsonpointer/blob/master/go.mod +[top-badge]: https://img.shields.io/github/languages/top/go-openapi/jsonpointer +[commits-badge]: https://img.shields.io/github/commits-since/go-openapi/jsonpointer/latest diff --git a/vendor/github.com/go-openapi/jsonpointer/SECURITY.md b/vendor/github.com/go-openapi/jsonpointer/SECURITY.md new file mode 100644 index 000000000..2a7b6f091 --- /dev/null +++ b/vendor/github.com/go-openapi/jsonpointer/SECURITY.md @@ -0,0 +1,19 @@ +# Security Policy + +This policy outlines the commitment and practices of the go-openapi maintainers regarding security. + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 0.22.x | :white_check_mark: | + +## Reporting a vulnerability + +If you become aware of a security vulnerability that affects the current repository, +please report it privately to the maintainers. + +Please follow the instructions provided by github to +[Privately report a security vulnerability](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability). + +TL;DR: on Github, navigate to the project's "Security" tab then click on "Report a vulnerability". diff --git a/vendor/github.com/go-openapi/jsonpointer/errors.go b/vendor/github.com/go-openapi/jsonpointer/errors.go index b84343d9d..8c50dde8b 100644 --- a/vendor/github.com/go-openapi/jsonpointer/errors.go +++ b/vendor/github.com/go-openapi/jsonpointer/errors.go @@ -1,5 +1,10 @@ +// SPDX-FileCopyrightText: Copyright (c) 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + package jsonpointer +import "fmt" + type pointerError string func (e pointerError) Error() string { @@ -7,12 +12,24 @@ func (e pointerError) Error() string { } const ( - // ErrPointer is an error raised by the jsonpointer package + // ErrPointer is a sentinel error raised by all errors from this package. ErrPointer pointerError = "JSON pointer error" - // ErrInvalidStart states that a JSON pointer must start with a separator ("/") + // ErrInvalidStart states that a JSON pointer must start with a separator ("/"). ErrInvalidStart pointerError = `JSON pointer must be empty or start with a "` + pointerSeparator - // ErrUnsupportedValueType indicates that a value of the wrong type is being set + // ErrUnsupportedValueType indicates that a value of the wrong type is being set. ErrUnsupportedValueType pointerError = "only structs, pointers, maps and slices are supported for setting values" ) + +func errNoKey(key string) error { + return fmt.Errorf("object has no key %q: %w", key, ErrPointer) +} + +func errOutOfBounds(length, idx int) error { + return fmt.Errorf("index out of bounds array[0,%d] index '%d': %w", length-1, idx, ErrPointer) +} + +func errInvalidReference(token string) error { + return fmt.Errorf("invalid token reference %q: %w", token, ErrPointer) +} diff --git a/vendor/github.com/go-openapi/jsonpointer/pointer.go b/vendor/github.com/go-openapi/jsonpointer/pointer.go index afc8a7840..7df49af3b 100644 --- a/vendor/github.com/go-openapi/jsonpointer/pointer.go +++ b/vendor/github.com/go-openapi/jsonpointer/pointer.go @@ -1,28 +1,7 @@ -// Copyright 2013 sigu-399 ( https://github.com/sigu-399 ) -// -// 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. - -// author sigu-399 -// author-github https://github.com/sigu-399 -// author-mail sigu.399@gmail.com -// -// repository-name jsonpointer -// repository-desc An implementation of JSON Pointer - Go language -// -// description Main and unique file. -// -// created 25-02-2013 +// SPDX-FileCopyrightText: Copyright (c) 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 +// Package jsonpointer provides a golang implementation for json pointers. package jsonpointer import ( @@ -41,70 +20,93 @@ const ( pointerSeparator = `/` ) -var jsonPointableType = reflect.TypeOf(new(JSONPointable)).Elem() -var jsonSetableType = reflect.TypeOf(new(JSONSetable)).Elem() - -// JSONPointable is an interface for structs to implement when they need to customize the -// json pointer process +// JSONPointable is an interface for structs to implement, +// when they need to customize the json pointer process or want to avoid the use of reflection. type JSONPointable interface { - JSONLookup(string) (any, error) + // JSONLookup returns a value pointed at this (unescaped) key. + JSONLookup(key string) (any, error) } -// JSONSetable is an interface for structs to implement when they need to customize the -// json pointer process +// JSONSetable is an interface for structs to implement, +// when they need to customize the json pointer process or want to avoid the use of reflection. type JSONSetable interface { - JSONSet(string, any) error + // JSONSet sets the value pointed at the (unescaped) key. + JSONSet(key string, value any) error } -// Pointer the json pointer reprsentation +// Pointer is a representation of a json pointer. +// +// Use [Pointer.Get] to retrieve a value or [Pointer.Set] to set a value. +// +// It works with any go type interpreted as a JSON document, which means: +// +// - if a type implements [JSONPointable], its [JSONPointable.JSONLookup] method is used to resolve [Pointer.Get] +// - if a type implements [JSONSetable], its [JSONPointable.JSONSet] method is used to resolve [Pointer.Set] +// - a go map[K]V is interpreted as an object, with type K assignable to a string +// - a go slice []T is interpreted as an array +// - a go struct is interpreted as an object, with exported fields interpreted as keys +// - promoted fields from an embedded struct are traversed +// - scalars (e.g. int, float64 ...), channels, functions and go arrays cannot be traversed +// +// For struct s resolved by reflection, key mappings honor the conventional struct tag `json`. +// +// Fields that do not specify a `json` tag, or specify an empty one, or are tagged as `json:"-"` are ignored. +// +// # Limitations +// +// - Unlike go standard marshaling, untagged fields do not default to the go field name and are ignored. +// - anonymous fields are not traversed if untagged type Pointer struct { referenceTokens []string } -// New creates a new json pointer for the given string +// New creates a new json pointer from its string representation. func New(jsonPointerString string) (Pointer, error) { - var p Pointer err := p.parse(jsonPointerString) - return p, err + return p, err } -// Get uses the pointer to retrieve a value from a JSON document +// Get uses the pointer to retrieve a value from a JSON document. +// +// It returns the value with its type as a [reflect.Kind] or an error. func (p *Pointer) Get(document any) (any, reflect.Kind, error) { return p.get(document, jsonname.DefaultJSONNameProvider) } -// Set uses the pointer to set a value from a JSON document +// Set uses the pointer to set a value from a data type +// that represent a JSON document. +// +// It returns the updated document. func (p *Pointer) Set(document any, value any) (any, error) { return document, p.set(document, value, jsonname.DefaultJSONNameProvider) } -// DecodedTokens returns the decoded tokens +// DecodedTokens returns the decoded (unescaped) tokens of this JSON pointer. func (p *Pointer) DecodedTokens() []string { result := make([]string, 0, len(p.referenceTokens)) - for _, t := range p.referenceTokens { - result = append(result, Unescape(t)) + for _, token := range p.referenceTokens { + result = append(result, Unescape(token)) } + return result } -// IsEmpty returns true if this is an empty json pointer -// this indicates that it points to the root document +// IsEmpty returns true if this is an empty json pointer. +// +// This indicates that it points to the root document. func (p *Pointer) IsEmpty() bool { return len(p.referenceTokens) == 0 } -// Pointer to string representation function +// String representation of a pointer. func (p *Pointer) String() string { - if len(p.referenceTokens) == 0 { return emptyPointer } - pointerString := pointerSeparator + strings.Join(p.referenceTokens, pointerSeparator) - - return pointerString + return pointerSeparator + strings.Join(p.referenceTokens, pointerSeparator) } func (p *Pointer) Offset(document string) (int64, error) { @@ -138,20 +140,21 @@ func (p *Pointer) Offset(document string) (int64, error) { return offset, nil } -// "Constructor", parses the given string JSON pointer +// "Constructor", parses the given string JSON pointer. func (p *Pointer) parse(jsonPointerString string) error { - var err error - - if jsonPointerString != emptyPointer { - if !strings.HasPrefix(jsonPointerString, pointerSeparator) { - err = errors.Join(ErrInvalidStart, ErrPointer) - } else { - referenceTokens := strings.Split(jsonPointerString, pointerSeparator) - p.referenceTokens = append(p.referenceTokens, referenceTokens[1:]...) - } + if jsonPointerString == emptyPointer { + return nil } - return err + if !strings.HasPrefix(jsonPointerString, pointerSeparator) { + // non empty pointer must start with "/" + return errors.Join(ErrInvalidStart, ErrPointer) + } + + referenceTokens := strings.Split(jsonPointerString, pointerSeparator) + p.referenceTokens = append(p.referenceTokens, referenceTokens[1:]...) + + return nil } func (p *Pointer) get(node any, nameProvider *jsonname.NameProvider) (any, reflect.Kind, error) { @@ -161,7 +164,7 @@ func (p *Pointer) get(node any, nameProvider *jsonname.NameProvider) (any, refle kind := reflect.Invalid - // Full document when empty + // full document when empty if len(p.referenceTokens) == 0 { return node, kind, nil } @@ -185,103 +188,105 @@ func (p *Pointer) get(node any, nameProvider *jsonname.NameProvider) (any, refle func (p *Pointer) set(node, data any, nameProvider *jsonname.NameProvider) error { knd := reflect.ValueOf(node).Kind() - if knd != reflect.Ptr && knd != reflect.Struct && knd != reflect.Map && knd != reflect.Slice && knd != reflect.Array { + if knd != reflect.Pointer && knd != reflect.Struct && knd != reflect.Map && knd != reflect.Slice && knd != reflect.Array { return errors.Join( + fmt.Errorf("unexpected type: %T", node), //nolint:err113 // err wrapping is carried out by errors.Join, not fmt.Errorf. ErrUnsupportedValueType, ErrPointer, ) } + l := len(p.referenceTokens) + + // full document when empty + if l == 0 { + return nil + } + if nameProvider == nil { nameProvider = jsonname.DefaultJSONNameProvider } - // Full document when empty - if len(p.referenceTokens) == 0 { - return nil + var decodedToken string + lastIndex := l - 1 + + if lastIndex > 0 { // skip if we only have one token in pointer + for _, token := range p.referenceTokens[:lastIndex] { + decodedToken = Unescape(token) + next, err := p.resolveNodeForToken(node, decodedToken, nameProvider) + if err != nil { + return err + } + + node = next + } } - lastI := len(p.referenceTokens) - 1 - for i, token := range p.referenceTokens { - isLastToken := i == lastI - decodedToken := Unescape(token) + // last token + decodedToken = Unescape(p.referenceTokens[lastIndex]) + + return setSingleImpl(node, data, decodedToken, nameProvider) +} - if isLastToken { +func (p *Pointer) resolveNodeForToken(node any, decodedToken string, nameProvider *jsonname.NameProvider) (next any, err error) { + // check for nil during traversal + if isNil(node) { + return nil, fmt.Errorf("cannot traverse through nil value at %q: %w", decodedToken, ErrPointer) + } - return setSingleImpl(node, data, decodedToken, nameProvider) + pointable, ok := node.(JSONPointable) + if ok { + r, err := pointable.JSONLookup(decodedToken) + if err != nil { + return nil, err } - // Check for nil during traversal - if isNil(node) { - return fmt.Errorf("cannot traverse through nil value at %q: %w", decodedToken, ErrPointer) + fld := reflect.ValueOf(r) + if fld.CanAddr() && fld.Kind() != reflect.Interface && fld.Kind() != reflect.Map && fld.Kind() != reflect.Slice && fld.Kind() != reflect.Pointer { + return fld.Addr().Interface(), nil } - rValue := reflect.Indirect(reflect.ValueOf(node)) - kind := rValue.Kind() + return r, nil + } - if rValue.Type().Implements(jsonPointableType) { - r, err := node.(JSONPointable).JSONLookup(decodedToken) - if err != nil { - return err - } - fld := reflect.ValueOf(r) - if fld.CanAddr() && fld.Kind() != reflect.Interface && fld.Kind() != reflect.Map && fld.Kind() != reflect.Slice && fld.Kind() != reflect.Ptr { - node = fld.Addr().Interface() - continue - } - node = r - continue + rValue := reflect.Indirect(reflect.ValueOf(node)) + kind := rValue.Kind() + + switch kind { + case reflect.Struct: + nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken) + if !ok { + return nil, fmt.Errorf("object has no field %q: %w", decodedToken, ErrPointer) } - switch kind { //nolint:exhaustive - case reflect.Struct: - nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken) - if !ok { - return fmt.Errorf("object has no field %q: %w", decodedToken, ErrPointer) - } - fld := rValue.FieldByName(nm) - if fld.CanAddr() && fld.Kind() != reflect.Interface && fld.Kind() != reflect.Map && fld.Kind() != reflect.Slice && fld.Kind() != reflect.Ptr { - node = fld.Addr().Interface() - continue - } - node = fld.Interface() + return typeFromValue(rValue.FieldByName(nm)), nil - case reflect.Map: - kv := reflect.ValueOf(decodedToken) - mv := rValue.MapIndex(kv) + case reflect.Map: + kv := reflect.ValueOf(decodedToken) + mv := rValue.MapIndex(kv) - if !mv.IsValid() { - return fmt.Errorf("object has no key %q: %w", decodedToken, ErrPointer) - } - if mv.CanAddr() && mv.Kind() != reflect.Interface && mv.Kind() != reflect.Map && mv.Kind() != reflect.Slice && mv.Kind() != reflect.Ptr { - node = mv.Addr().Interface() - continue - } - node = mv.Interface() + if !mv.IsValid() { + return nil, errNoKey(decodedToken) + } - case reflect.Slice: - tokenIndex, err := strconv.Atoi(decodedToken) - if err != nil { - return err - } - sLength := rValue.Len() - if tokenIndex < 0 || tokenIndex >= sLength { - return fmt.Errorf("index out of bounds array[0,%d] index '%d': %w", sLength, tokenIndex, ErrPointer) - } + return typeFromValue(mv), nil - elem := rValue.Index(tokenIndex) - if elem.CanAddr() && elem.Kind() != reflect.Interface && elem.Kind() != reflect.Map && elem.Kind() != reflect.Slice && elem.Kind() != reflect.Ptr { - node = elem.Addr().Interface() - continue - } - node = elem.Interface() + case reflect.Slice: + tokenIndex, err := strconv.Atoi(decodedToken) + if err != nil { + return nil, errors.Join(err, ErrPointer) + } - default: - return fmt.Errorf("invalid token reference %q: %w", decodedToken, ErrPointer) + sLength := rValue.Len() + if tokenIndex < 0 || tokenIndex >= sLength { + return nil, errOutOfBounds(sLength, tokenIndex) } - } - return nil + return typeFromValue(rValue.Index(tokenIndex)), nil + + default: + return nil, errInvalidReference(decodedToken) + } } func isNil(input any) bool { @@ -290,20 +295,28 @@ func isNil(input any) bool { } kind := reflect.TypeOf(input).Kind() - switch kind { //nolint:exhaustive - case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Chan: + switch kind { + case reflect.Pointer, reflect.Map, reflect.Slice, reflect.Chan: return reflect.ValueOf(input).IsNil() default: return false } } -// GetForToken gets a value for a json pointer token 1 level deep +func typeFromValue(v reflect.Value) any { + if v.CanAddr() && v.Kind() != reflect.Interface && v.Kind() != reflect.Map && v.Kind() != reflect.Slice && v.Kind() != reflect.Pointer { + return v.Addr().Interface() + } + + return v.Interface() +} + +// GetForToken gets a value for a json pointer token 1 level deep. func GetForToken(document any, decodedToken string) (any, reflect.Kind, error) { return getSingleImpl(document, decodedToken, jsonname.DefaultJSONNameProvider) } -// SetForToken gets a value for a json pointer token 1 level deep +// SetForToken sets a value for a json pointer token 1 level deep. func SetForToken(document any, decodedToken string, value any) (any, error) { return document, setSingleImpl(document, value, decodedToken, jsonname.DefaultJSONNameProvider) } @@ -326,13 +339,15 @@ func getSingleImpl(node any, decodedToken string, nameProvider *jsonname.NamePro return getSingleImpl(*typed, decodedToken, nameProvider) } - switch kind { //nolint:exhaustive + switch kind { case reflect.Struct: nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken) if !ok { return nil, kind, fmt.Errorf("object has no field %q: %w", decodedToken, ErrPointer) } + fld := rValue.FieldByName(nm) + return fld.Interface(), kind, nil case reflect.Map: @@ -342,78 +357,99 @@ func getSingleImpl(node any, decodedToken string, nameProvider *jsonname.NamePro if mv.IsValid() { return mv.Interface(), kind, nil } - return nil, kind, fmt.Errorf("object has no key %q: %w", decodedToken, ErrPointer) + + return nil, kind, errNoKey(decodedToken) case reflect.Slice: tokenIndex, err := strconv.Atoi(decodedToken) if err != nil { - return nil, kind, err + return nil, kind, errors.Join(err, ErrPointer) } sLength := rValue.Len() if tokenIndex < 0 || tokenIndex >= sLength { - return nil, kind, fmt.Errorf("index out of bounds array[0,%d] index '%d': %w", sLength-1, tokenIndex, ErrPointer) + return nil, kind, errOutOfBounds(sLength, tokenIndex) } elem := rValue.Index(tokenIndex) return elem.Interface(), kind, nil default: - return nil, kind, fmt.Errorf("invalid token reference %q: %w", decodedToken, ErrPointer) + return nil, kind, errInvalidReference(decodedToken) } } func setSingleImpl(node, data any, decodedToken string, nameProvider *jsonname.NameProvider) error { - rValue := reflect.Indirect(reflect.ValueOf(node)) - - // Check for nil to prevent panic when calling rValue.Type() + // check for nil to prevent panic when calling rValue.Type() if isNil(node) { return fmt.Errorf("cannot set field %q on nil value: %w", decodedToken, ErrPointer) } - if ns, ok := node.(JSONSetable); ok { // pointer impl + if ns, ok := node.(JSONSetable); ok { return ns.JSONSet(decodedToken, data) } - if rValue.Type().Implements(jsonSetableType) { - return node.(JSONSetable).JSONSet(decodedToken, data) - } + rValue := reflect.Indirect(reflect.ValueOf(node)) - switch rValue.Kind() { //nolint:exhaustive + switch rValue.Kind() { case reflect.Struct: nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken) if !ok { return fmt.Errorf("object has no field %q: %w", decodedToken, ErrPointer) } + fld := rValue.FieldByName(nm) - if fld.IsValid() { - fld.Set(reflect.ValueOf(data)) + if !fld.CanSet() { + return fmt.Errorf("can't set struct field %s to %v: %w", nm, data, ErrPointer) } + + value := reflect.ValueOf(data) + valueType := value.Type() + assignedType := fld.Type() + + if !valueType.AssignableTo(assignedType) { + return fmt.Errorf("can't set value with type %T to field %s with type %v: %w", data, nm, assignedType, ErrPointer) + } + + fld.Set(value) + return nil case reflect.Map: kv := reflect.ValueOf(decodedToken) rValue.SetMapIndex(kv, reflect.ValueOf(data)) + return nil case reflect.Slice: tokenIndex, err := strconv.Atoi(decodedToken) if err != nil { - return err + return errors.Join(err, ErrPointer) } + sLength := rValue.Len() if tokenIndex < 0 || tokenIndex >= sLength { - return fmt.Errorf("index out of bounds array[0,%d] index '%d': %w", sLength, tokenIndex, ErrPointer) + return errOutOfBounds(sLength, tokenIndex) } elem := rValue.Index(tokenIndex) if !elem.CanSet() { return fmt.Errorf("can't set slice index %s to %v: %w", decodedToken, data, ErrPointer) } - elem.Set(reflect.ValueOf(data)) + + value := reflect.ValueOf(data) + valueType := value.Type() + assignedType := elem.Type() + + if !valueType.AssignableTo(assignedType) { + return fmt.Errorf("can't set value with type %T to slice element %d with type %v: %w", data, tokenIndex, assignedType, ErrPointer) + } + + elem.Set(value) + return nil default: - return fmt.Errorf("invalid token reference %q: %w", decodedToken, ErrPointer) + return errInvalidReference(decodedToken) } } @@ -444,13 +480,14 @@ func offsetSingleObject(dec *json.Decoder, decodedToken string) (int64, error) { return 0, fmt.Errorf("invalid token %#v: %w", tk, ErrPointer) } } + return 0, fmt.Errorf("token reference %q not found: %w", decodedToken, ErrPointer) } func offsetSingleArray(dec *json.Decoder, decodedToken string) (int64, error) { idx, err := strconv.Atoi(decodedToken) if err != nil { - return 0, fmt.Errorf("token reference %q is not a number: %v: %w", decodedToken, err, ErrPointer) + return 0, fmt.Errorf("token reference %q is not a number: %w: %w", decodedToken, err, ErrPointer) } var i int for i = 0; i < idx && dec.More(); i++ { @@ -476,10 +513,12 @@ func offsetSingleArray(dec *json.Decoder, decodedToken string) (int64, error) { if !dec.More() { return 0, fmt.Errorf("token reference %q not found: %w", decodedToken, ErrPointer) } + return dec.InputOffset(), nil } // drainSingle drains a single level of object or array. +// // The decoder has to guarantee the beginning delim (i.e. '{' or '[') has been consumed. func drainSingle(dec *json.Decoder) error { for dec.More() { @@ -501,14 +540,15 @@ func drainSingle(dec *json.Decoder) error { } } - // Consumes the ending delim + // consumes the ending delim if _, err := dec.Token(); err != nil { return err } + return nil } -// Specific JSON pointer encoding here +// JSON pointer encoding: // ~0 => ~ // ~1 => / // ... and vice versa @@ -520,16 +560,24 @@ const ( decRefTok1 = `/` ) -// Unescape unescapes a json pointer reference token string to the original representation +var ( + encRefTokReplacer = strings.NewReplacer(encRefTok1, decRefTok1, encRefTok0, decRefTok0) //nolint:gochecknoglobals // it's okay to declare a replacer as a private global + decRefTokReplacer = strings.NewReplacer(decRefTok1, encRefTok1, decRefTok0, encRefTok0) //nolint:gochecknoglobals // it's okay to declare a replacer as a private global +) + +// Unescape unescapes a json pointer reference token string to the original representation. func Unescape(token string) string { - step1 := strings.ReplaceAll(token, encRefTok1, decRefTok1) - step2 := strings.ReplaceAll(step1, encRefTok0, decRefTok0) - return step2 + return encRefTokReplacer.Replace(token) } -// Escape escapes a pointer reference token string +// Escape escapes a pointer reference token string. +// +// The JSONPointer specification defines "/" as a separator and "~" as an escape prefix. +// +// Keys containing such characters are escaped with the following rules: +// +// - "~" is escaped as "~0" +// - "/" is escaped as "~1" func Escape(token string) string { - step1 := strings.ReplaceAll(token, decRefTok0, encRefTok0) - step2 := strings.ReplaceAll(step1, decRefTok1, encRefTok1) - return step2 + return decRefTokReplacer.Replace(token) } diff --git a/vendor/github.com/go-openapi/jsonreference/.cliff.toml b/vendor/github.com/go-openapi/jsonreference/.cliff.toml new file mode 100644 index 000000000..702629f5d --- /dev/null +++ b/vendor/github.com/go-openapi/jsonreference/.cliff.toml @@ -0,0 +1,181 @@ +# git-cliff ~ configuration file +# https://git-cliff.org/docs/configuration + +[changelog] +header = """ +""" + +footer = """ + +----- + +**[{{ remote.github.repo }}]({{ self::remote_url() }}) license terms** + +[![License][license-badge]][license-url] + +[license-badge]: http://img.shields.io/badge/license-Apache%20v2-orange.svg +[license-url]: {{ self::remote_url() }}/?tab=Apache-2.0-1-ov-file#readme + +{%- macro remote_url() -%} + https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} +{%- endmacro -%} +""" + +body = """ +{%- if version %} +## [{{ version | trim_start_matches(pat="v") }}]({{ self::remote_url() }}/tree/{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }} +{%- else %} +## [unreleased] +{%- endif %} +{%- if message %} + {%- raw %}\n{% endraw %} +{{ message }} + {%- raw %}\n{% endraw %} +{%- endif %} +{%- if version %} + {%- if previous.version %} + +**Full Changelog**: <{{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }}> + {%- endif %} +{%- else %} + {%- raw %}\n{% endraw %} +{%- endif %} + +{%- if statistics %}{% if statistics.commit_count %} + {%- raw %}\n{% endraw %} +{{ statistics.commit_count }} commits in this release. + {%- raw %}\n{% endraw %} +{%- endif %}{% endif %} +----- + +{%- for group, commits in commits | group_by(attribute="group") %} + {%- raw %}\n{% endraw %} +### {{ group | upper_first }} + {%- raw %}\n{% endraw %} + {%- for commit in commits %} + {%- if commit.remote.pr_title %} + {%- set commit_message = commit.remote.pr_title %} + {%- else %} + {%- set commit_message = commit.message %} + {%- endif %} +* {{ commit_message | split(pat="\n") | first | trim }} + {%- if commit.remote.username %} +{%- raw %} {% endraw %}by [@{{ commit.remote.username }}](https://github.com/{{ commit.remote.username }}) + {%- endif %} + {%- if commit.remote.pr_number %} +{%- raw %} {% endraw %}in [#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }}) + {%- endif %} +{%- raw %} {% endraw %}[...]({{ self::remote_url() }}/commit/{{ commit.id }}) + {%- endfor %} +{%- endfor %} + +{%- if github %} +{%- raw %}\n{% endraw -%} + {%- set all_contributors = github.contributors | length %} + {%- if github.contributors | filter(attribute="username", value="dependabot[bot]") | length < all_contributors %} +----- + +### People who contributed to this release + {% endif %} + {%- for contributor in github.contributors | filter(attribute="username") | sort(attribute="username") %} + {%- if contributor.username != "dependabot[bot]" and contributor.username != "github-actions[bot]" %} +* [@{{ contributor.username }}](https://github.com/{{ contributor.username }}) + {%- endif %} + {%- endfor %} + + {% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %} +----- + {%- raw %}\n{% endraw %} + +### New Contributors + {%- endif %} + + {%- for contributor in github.contributors | filter(attribute="is_first_time", value=true) %} + {%- if contributor.username != "dependabot[bot]" and contributor.username != "github-actions[bot]" %} +* @{{ contributor.username }} made their first contribution + {%- if contributor.pr_number %} + in [#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \ + {%- endif %} + {%- endif %} + {%- endfor %} +{%- endif %} + +{%- raw %}\n{% endraw %} + +{%- macro remote_url() -%} + https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} +{%- endmacro -%} +""" +# Remove leading and trailing whitespaces from the changelog's body. +trim = true +# Render body even when there are no releases to process. +render_always = true +# An array of regex based postprocessors to modify the changelog. +postprocessors = [ + # Replace the placeholder with a URL. + #{ pattern = '', replace = "https://github.com/orhun/git-cliff" }, +] +# output file path +# output = "test.md" + +[git] +# Parse commits according to the conventional commits specification. +# See https://www.conventionalcommits.org +conventional_commits = false +# Exclude commits that do not match the conventional commits specification. +filter_unconventional = false +# Require all commits to be conventional. +# Takes precedence over filter_unconventional. +require_conventional = false +# Split commits on newlines, treating each line as an individual commit. +split_commits = false +# An array of regex based parsers to modify commit messages prior to further processing. +commit_preprocessors = [ + # Replace issue numbers with link templates to be updated in `changelog.postprocessors`. + #{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))"}, + # Check spelling of the commit message using https://github.com/crate-ci/typos. + # If the spelling is incorrect, it will be fixed automatically. + #{ pattern = '.*', replace_command = 'typos --write-changes -' } +] +# Prevent commits that are breaking from being excluded by commit parsers. +protect_breaking_commits = false +# An array of regex based parsers for extracting data from the commit message. +# Assigns commits to groups. +# Optionally sets the commit's scope and can decide to exclude commits from further processing. +commit_parsers = [ + { message = "^[Cc]hore\\([Rr]elease\\): prepare for", skip = true }, + { message = "(^[Mm]erge)|([Mm]erge conflict)", skip = true }, + { field = "author.name", pattern = "dependabot*", group = "Updates" }, + { message = "([Ss]ecurity)|([Vv]uln)", group = "Security" }, + { body = "(.*[Ss]ecurity)|([Vv]uln)", group = "Security" }, + { message = "([Cc]hore\\(lint\\))|(style)|(lint)|(codeql)|(golangci)", group = "Code quality" }, + { message = "(^[Dd]oc)|((?i)readme)|(badge)|(typo)|(documentation)", group = "Documentation" }, + { message = "(^[Ff]eat)|(^[Ee]nhancement)", group = "Implemented enhancements" }, + { message = "(^ci)|(\\(ci\\))|(fixup\\s+ci)|(fix\\s+ci)|(license)|(example)", group = "Miscellaneous tasks" }, + { message = "^test", group = "Testing" }, + { message = "(^fix)|(panic)", group = "Fixed bugs" }, + { message = "(^refact)|(rework)", group = "Refactor" }, + { message = "(^[Pp]erf)|(performance)", group = "Performance" }, + { message = "(^[Cc]hore)", group = "Miscellaneous tasks" }, + { message = "^[Rr]evert", group = "Reverted changes" }, + { message = "(upgrade.*?go)|(go\\s+version)", group = "Updates" }, + { message = ".*", group = "Other" }, +] +# Exclude commits that are not matched by any commit parser. +filter_commits = false +# An array of link parsers for extracting external references, and turning them into URLs, using regex. +link_parsers = [] +# Include only the tags that belong to the current branch. +use_branch_tags = false +# Order releases topologically instead of chronologically. +topo_order = false +# Order releases topologically instead of chronologically. +topo_order_commits = true +# Order of commits in each group/release within the changelog. +# Allowed values: newest, oldest +sort_commits = "newest" +# Process submodules commits +recurse_submodules = false + +#[remote.github] +#owner = "go-openapi" diff --git a/vendor/github.com/go-openapi/jsonreference/.editorconfig b/vendor/github.com/go-openapi/jsonreference/.editorconfig new file mode 100644 index 000000000..3152da69a --- /dev/null +++ b/vendor/github.com/go-openapi/jsonreference/.editorconfig @@ -0,0 +1,26 @@ +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +# Set default charset +[*.{js,py,go,scala,rb,java,html,css,less,sass,md}] +charset = utf-8 + +# Tab indentation (no size specified) +[*.go] +indent_style = tab + +[*.md] +trim_trailing_whitespace = false + +# Matches the exact files either package.json or .travis.yml +[{package.json,.travis.yml}] +indent_style = space +indent_size = 2 diff --git a/vendor/github.com/go-openapi/jsonreference/.golangci.yml b/vendor/github.com/go-openapi/jsonreference/.golangci.yml index 568ce16d7..fdae591bc 100644 --- a/vendor/github.com/go-openapi/jsonreference/.golangci.yml +++ b/vendor/github.com/go-openapi/jsonreference/.golangci.yml @@ -2,34 +2,17 @@ version: "2" linters: default: all disable: - - cyclop - depguard - - errchkjson - - errorlint - - exhaustruct - - forcetypeassert - funlen - - gochecknoglobals - - gochecknoinits - - gocognit - - godot - godox - - gosmopolitan - - inamedparam - - intrange # disabled while < go1.22 - - ireturn - - lll - - musttag - - nestif + - exhaustruct - nlreturn - nonamedreturns - noinlineerr - paralleltest - recvcheck - testpackage - - thelper - tparallel - - unparam - varnamelen - whitespace - wrapcheck @@ -41,8 +24,15 @@ linters: goconst: min-len: 2 min-occurrences: 3 + cyclop: + max-complexity: 20 gocyclo: - min-complexity: 45 + min-complexity: 20 + exhaustive: + default-signifies-exhaustive: true + default-case-required: true + lll: + line-length: 180 exclusions: generated: lax presets: @@ -58,6 +48,7 @@ formatters: enable: - gofmt - goimports + - gofumpt exclusions: generated: lax paths: diff --git a/vendor/github.com/go-openapi/jsonreference/CONTRIBUTORS.md b/vendor/github.com/go-openapi/jsonreference/CONTRIBUTORS.md new file mode 100644 index 000000000..9907d5d21 --- /dev/null +++ b/vendor/github.com/go-openapi/jsonreference/CONTRIBUTORS.md @@ -0,0 +1,21 @@ +# Contributors + +- Repository: ['go-openapi/jsonreference'] + +| Total Contributors | Total Contributions | +| --- | --- | +| 9 | 68 | + +| Username | All Time Contribution Count | All Commits | +| --- | --- | --- | +| @fredbi | 31 | https://github.com/go-openapi/jsonreference/commits?author=fredbi | +| @casualjim | 25 | https://github.com/go-openapi/jsonreference/commits?author=casualjim | +| @youyuanwu | 5 | https://github.com/go-openapi/jsonreference/commits?author=youyuanwu | +| @olivierlemasle | 2 | https://github.com/go-openapi/jsonreference/commits?author=olivierlemasle | +| @apelisse | 1 | https://github.com/go-openapi/jsonreference/commits?author=apelisse | +| @gbjk | 1 | https://github.com/go-openapi/jsonreference/commits?author=gbjk | +| @honza | 1 | https://github.com/go-openapi/jsonreference/commits?author=honza | +| @Neo2308 | 1 | https://github.com/go-openapi/jsonreference/commits?author=Neo2308 | +| @erraggy | 1 | https://github.com/go-openapi/jsonreference/commits?author=erraggy | + + _this file was generated by the [Contributors GitHub Action](https://github.com/github/contributors)_ diff --git a/vendor/github.com/go-openapi/jsonreference/NOTICE b/vendor/github.com/go-openapi/jsonreference/NOTICE new file mode 100644 index 000000000..f3b51939a --- /dev/null +++ b/vendor/github.com/go-openapi/jsonreference/NOTICE @@ -0,0 +1,39 @@ +Copyright 2015-2025 go-swagger maintainers + +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +This software library, github.com/go-openapi/jsonpointer, includes software developed +by the go-swagger and go-openapi maintainers ("go-swagger maintainers"). + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this software except in compliance with the License. + +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0. + +This software is copied from, derived from, and inspired by other original software products. +It ships with copies of other software which license terms are recalled below. + +The original software was authored on 25-02-2013 by sigu-399 (https://github.com/sigu-399, sigu.399@gmail.com). + +github.com/sigh-399/jsonpointer +=========================== + +// SPDX-FileCopyrightText: Copyright 2013 sigu-399 ( https://github.com/sigu-399 ) +// SPDX-License-Identifier: Apache-2.0 + +Copyright 2013 sigu-399 ( https://github.com/sigu-399 ) + +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. diff --git a/vendor/github.com/go-openapi/jsonreference/README.md b/vendor/github.com/go-openapi/jsonreference/README.md index c7fc2049c..d479dbdc7 100644 --- a/vendor/github.com/go-openapi/jsonreference/README.md +++ b/vendor/github.com/go-openapi/jsonreference/README.md @@ -1,19 +1,99 @@ -# gojsonreference [![Build Status](https://github.com/go-openapi/jsonreference/actions/workflows/go-test.yml/badge.svg)](https://github.com/go-openapi/jsonreference/actions?query=workflow%3A"go+test") [![codecov](https://codecov.io/gh/go-openapi/jsonreference/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/jsonreference) +# jsonreference -[![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io) -[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/jsonreference/master/LICENSE) -[![Go Reference](https://pkg.go.dev/badge/github.com/go-openapi/jsonreference.svg)](https://pkg.go.dev/github.com/go-openapi/jsonreference) -[![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/jsonreference)](https://goreportcard.com/report/github.com/go-openapi/jsonreference) + +[![Tests][test-badge]][test-url] [![Coverage][cov-badge]][cov-url] [![CI vuln scan][vuln-scan-badge]][vuln-scan-url] [![CodeQL][codeql-badge]][codeql-url] + + + +[![Release][release-badge]][release-url] [![Go Report Card][gocard-badge]][gocard-url] [![CodeFactor Grade][codefactor-badge]][codefactor-url] [![License][license-badge]][license-url] + + +[![GoDoc][godoc-badge]][godoc-url] [![Slack Channel][slack-logo]![slack-badge]][slack-url] [![go version][goversion-badge]][goversion-url] ![Top language][top-badge] ![Commits since latest release][commits-badge] -An implementation of JSON Reference - Go language +--- + +An implementation of JSON Reference for golang. ## Status -Feature complete. Stable API + +API is stable. + +## Import this library in your project + +```cmd +go get github.com/go-openapi/jsonreference +``` ## Dependencies + * https://github.com/go-openapi/jsonpointer +## Basic usage + +## Change log + +See + ## References * http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07 * http://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03 + +## Licensing + +This library ships under the [SPDX-License-Identifier: Apache-2.0](./LICENSE). + +See the license [NOTICE](./NOTICE), which recalls the licensing terms of all the pieces of software +on top of which it has been built. + +## Other documentation + +* [All-time contributors](./CONTRIBUTORS.md) +* [Contributing guidelines](.github/CONTRIBUTING.md) +* [Maintainers documentation](docs/MAINTAINERS.md) +* [Code style](docs/STYLE.md) + +## Cutting a new release + +Maintainers can cut a new release by either: + +* running [this workflow](https://github.com/go-openapi/jsonreference/actions/workflows/bump-release.yml) +* or pushing a semver tag + * signed tags are preferred + * The tag message is prepended to release notes + + +[test-badge]: https://github.com/go-openapi/jsonreference/actions/workflows/go-test.yml/badge.svg +[test-url]: https://github.com/go-openapi/jsonreference/actions/workflows/go-test.yml +[cov-badge]: https://codecov.io/gh/go-openapi/jsonreference/branch/master/graph/badge.svg +[cov-url]: https://codecov.io/gh/go-openapi/jsonreference +[vuln-scan-badge]: https://github.com/go-openapi/jsonreference/actions/workflows/scanner.yml/badge.svg +[vuln-scan-url]: https://github.com/go-openapi/jsonreference/actions/workflows/scanner.yml +[codeql-badge]: https://github.com/go-openapi/jsonreference/actions/workflows/codeql.yml/badge.svg +[codeql-url]: https://github.com/go-openapi/jsonreference/actions/workflows/codeql.yml + +[release-badge]: https://badge.fury.io/gh/go-openapi%2Fjsonreference.svg +[release-url]: https://badge.fury.io/gh/go-openapi%2Fjsonreference +[gomod-badge]: https://badge.fury.io/go/github.com%2Fgo-openapi%2Fjsonreference.svg +[gomod-url]: https://badge.fury.io/go/github.com%2Fgo-openapi%2Fjsonreference + +[gocard-badge]: https://goreportcard.com/badge/github.com/go-openapi/jsonreference +[gocard-url]: https://goreportcard.com/report/github.com/go-openapi/jsonreference +[codefactor-badge]: https://img.shields.io/codefactor/grade/github/go-openapi/jsonreference +[codefactor-url]: https://www.codefactor.io/repository/github/go-openapi/jsonreference + +[doc-badge]: https://img.shields.io/badge/doc-site-blue?link=https%3A%2F%2Fgoswagger.io%2Fgo-openapi%2F +[doc-url]: https://goswagger.io/go-openapi +[godoc-badge]: https://pkg.go.dev/badge/github.com/go-openapi/jsonreference +[godoc-url]: http://pkg.go.dev/github.com/go-openapi/jsonreference +[slack-logo]: https://a.slack-edge.com/e6a93c1/img/icons/favicon-32.png +[slack-badge]: https://img.shields.io/badge/slack-blue?link=https%3A%2F%2Fgoswagger.slack.com%2Farchives%2FC04R30YM +[slack-url]: https://goswagger.slack.com/archives/C04R30YMU + +[license-badge]: http://img.shields.io/badge/license-Apache%20v2-orange.svg +[license-url]: https://github.com/go-openapi/jsonreference/?tab=Apache-2.0-1-ov-file#readme + +[goversion-badge]: https://img.shields.io/github/go-mod/go-version/go-openapi/jsonreference +[goversion-url]: https://github.com/go-openapi/jsonreference/blob/master/go.mod +[top-badge]: https://img.shields.io/github/languages/top/go-openapi/jsonreference +[commits-badge]: https://img.shields.io/github/commits-since/go-openapi/jsonreference/latest diff --git a/vendor/github.com/go-openapi/jsonreference/SECURITY.md b/vendor/github.com/go-openapi/jsonreference/SECURITY.md new file mode 100644 index 000000000..2a7b6f091 --- /dev/null +++ b/vendor/github.com/go-openapi/jsonreference/SECURITY.md @@ -0,0 +1,19 @@ +# Security Policy + +This policy outlines the commitment and practices of the go-openapi maintainers regarding security. + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 0.22.x | :white_check_mark: | + +## Reporting a vulnerability + +If you become aware of a security vulnerability that affects the current repository, +please report it privately to the maintainers. + +Please follow the instructions provided by github to +[Privately report a security vulnerability](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability). + +TL;DR: on Github, navigate to the project's "Security" tab then click on "Report a vulnerability". diff --git a/vendor/github.com/go-openapi/jsonreference/internal/normalize_url.go b/vendor/github.com/go-openapi/jsonreference/internal/normalize_url.go index f0610cf1e..a08b47320 100644 --- a/vendor/github.com/go-openapi/jsonreference/internal/normalize_url.go +++ b/vendor/github.com/go-openapi/jsonreference/internal/normalize_url.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright (c) 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + package internal import ( @@ -11,9 +14,11 @@ const ( defaultHTTPSPort = ":443" ) -// Regular expressions used by the normalizations -var rxPort = regexp.MustCompile(`(:\d+)/?$`) -var rxDupSlashes = regexp.MustCompile(`/{2,}`) +// Regular expressions used by the normalizations. +var ( + rxPort = regexp.MustCompile(`(:\d+)/?$`) + rxDupSlashes = regexp.MustCompile(`/{2,}`) +) // NormalizeURL will normalize the specified URL // This was added to replace a previous call to the no longer maintained purell library: diff --git a/vendor/github.com/go-openapi/jsonreference/reference.go b/vendor/github.com/go-openapi/jsonreference/reference.go index 6a1fed5df..6e3ae4995 100644 --- a/vendor/github.com/go-openapi/jsonreference/reference.go +++ b/vendor/github.com/go-openapi/jsonreference/reference.go @@ -1,27 +1,5 @@ -// Copyright 2013 sigu-399 ( https://github.com/sigu-399 ) -// -// 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. - -// author sigu-399 -// author-github https://github.com/sigu-399 -// author-mail sigu.399@gmail.com -// -// repository-name jsonreference -// repository-desc An implementation of JSON Reference - Go language -// -// description Main and unique file. -// -// created 26-02-2013 +// SPDX-FileCopyrightText: Copyright (c) 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package jsonreference @@ -40,7 +18,7 @@ const ( var ErrChildURL = errors.New("child url is nil") -// Ref represents a json reference object +// Ref represents a json reference object. type Ref struct { referenceURL *url.URL referencePointer jsonpointer.Pointer @@ -52,7 +30,7 @@ type Ref struct { HasFullFilePath bool } -// New creates a new reference for the given string +// New creates a new reference for the given string. func New(jsonReferenceString string) (Ref, error) { var r Ref err := r.parse(jsonReferenceString) @@ -60,7 +38,7 @@ func New(jsonReferenceString string) (Ref, error) { } // MustCreateRef parses the ref string and panics when it's invalid. -// Use the New method for a version that returns an error +// Use the New method for a version that returns an error. func MustCreateRef(ref string) Ref { r, err := New(ref) if err != nil { @@ -70,17 +48,17 @@ func MustCreateRef(ref string) Ref { return r } -// GetURL gets the URL for this reference +// GetURL gets the URL for this reference. func (r *Ref) GetURL() *url.URL { return r.referenceURL } -// GetPointer gets the json pointer for this reference +// GetPointer gets the json pointer for this reference. func (r *Ref) GetPointer() *jsonpointer.Pointer { return &r.referencePointer } -// String returns the best version of the url for this reference +// String returns the best version of the url for this reference. func (r *Ref) String() string { if r.referenceURL != nil { return r.referenceURL.String() @@ -93,7 +71,7 @@ func (r *Ref) String() string { return r.referencePointer.String() } -// IsRoot returns true if this reference is a root document +// IsRoot returns true if this reference is a root document. func (r *Ref) IsRoot() bool { return r.referenceURL != nil && !r.IsCanonical() && @@ -101,13 +79,13 @@ func (r *Ref) IsRoot() bool { r.referenceURL.Fragment == "" } -// IsCanonical returns true when this pointer starts with http(s):// or file:// +// IsCanonical returns true when this pointer starts with http(s):// or file://. func (r *Ref) IsCanonical() bool { return (r.HasFileScheme && r.HasFullFilePath) || (!r.HasFileScheme && r.HasFullURL) } // Inherits creates a new reference from a parent and a child -// If the child cannot inherit from the parent, an error is returned +// If the child cannot inherit from the parent, an error is returned. func (r *Ref) Inherits(child Ref) (*Ref, error) { childURL := child.GetURL() parentURL := r.GetURL() @@ -125,7 +103,7 @@ func (r *Ref) Inherits(child Ref) (*Ref, error) { return &ref, nil } -// "Constructor", parses the given string JSON reference +// "Constructor", parses the given string JSON reference. func (r *Ref) parse(jsonReferenceString string) error { parsed, err := url.Parse(jsonReferenceString) if err != nil { diff --git a/vendor/github.com/go-openapi/swag/.codecov.yml b/vendor/github.com/go-openapi/swag/.codecov.yml new file mode 100644 index 000000000..3354f44b2 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/.codecov.yml @@ -0,0 +1,4 @@ +ignore: + - jsonutils/fixtures_test + - jsonutils/adapters/ifaces/mocks + - jsonutils/adapters/testintegration/benchmarks diff --git a/vendor/github.com/go-openapi/swag/.golangci.yml b/vendor/github.com/go-openapi/swag/.golangci.yml index 66eac0df8..126264a6b 100644 --- a/vendor/github.com/go-openapi/swag/.golangci.yml +++ b/vendor/github.com/go-openapi/swag/.golangci.yml @@ -17,10 +17,11 @@ linters: - gomoddirectives - gosmopolitan - inamedparam - - intrange # disabled while < go1.22 + - intrange - ireturn - lll - musttag + - modernize - nestif - nlreturn - nonamedreturns @@ -29,6 +30,7 @@ linters: - recvcheck - testpackage - thelper + - tagliatelle - tparallel - unparam - varnamelen diff --git a/vendor/github.com/go-openapi/swag/.mockery.yml b/vendor/github.com/go-openapi/swag/.mockery.yml new file mode 100644 index 000000000..8557cb58d --- /dev/null +++ b/vendor/github.com/go-openapi/swag/.mockery.yml @@ -0,0 +1,30 @@ +all: false +dir: '{{.InterfaceDir}}' +filename: mocks_test.go +force-file-write: true +formatter: goimports +include-auto-generated: false +log-level: info +structname: '{{.Mock}}{{.InterfaceName}}' +pkgname: '{{.SrcPackageName}}' +recursive: false +require-template-schema-exists: true +template: matryer +template-schema: '{{.Template}}.schema.json' +packages: + github.com/go-openapi/swag/jsonutils/adapters/ifaces: + config: + dir: jsonutils/adapters/ifaces/mocks + filename: mocks.go + pkgname: 'mocks' + force-file-write: true + all: true + github.com/go-openapi/swag/jsonutils/adapters/testintegration: + config: + inpackage: true + dir: jsonutils/adapters/testintegration + force-file-write: true + all: true + interfaces: + EJMarshaler: + EJUnmarshaler: diff --git a/vendor/github.com/go-openapi/swag/README.md b/vendor/github.com/go-openapi/swag/README.md index 1efb7a342..371fd55fd 100644 --- a/vendor/github.com/go-openapi/swag/README.md +++ b/vendor/github.com/go-openapi/swag/README.md @@ -9,91 +9,146 @@ Package `swag` contains a bunch of helper functions for go-openapi and go-swagge You may also use it standalone for your projects. +> **NOTE** > `swag` is one of the foundational building blocks of the go-openapi initiative. -> > Most repositories in `github.com/go-openapi/...` depend on it in some way. -> So does the CLI tool `github.com/go-swagger/go-swagger`, -> and the code generated by this tool. +> And so does our CLI tool `github.com/go-swagger/go-swagger`, +> as well as the code generated by this tool. + +* [Contents](#contents) +* [Dependencies](#dependencies) +* [Release Notes](#release-notes) +* [Licensing](#licensing) +* [Note to contributors](#note-to-contributors) +* [TODOs, suggestions and plans](#todos-suggestions-and-plans) ## Contents -`go-openapi/swag` now exposes a collection of relatively independent modules. +`go-openapi/swag` exposes a collection of relatively independent modules. -Here is what is inside: +Moving forward, no additional feature will be added to the `swag` API directly at the root package level, +which remains there for backward-compatibility purposes. All exported top-level features are now deprecated. -* Module `cmdutils` +Child modules will continue to evolve and some new ones may be added in the future. - * [x] utilities to work with CLIs +| Module | Content | Main features | +|---------------|---------|---------------| +| `cmdutils` | utilities to work with CLIs || +| `conv` | type conversion utilities | convert between values and pointers for any types
convert from string to builtin types (wraps `strconv`)
require `./typeutils` (test dependency)
| +| `fileutils` | file utilities | | +| `jsonname` | JSON utilities | infer JSON names from `go` properties
| +| `jsonutils` | JSON utilities | fast json concatenation
read and write JSON from and to dynamic `go` data structures
~require `github.com/mailru/easyjson`~
| +| `loading` | file loading | load from file or http
require `./yamlutils`
| +| `mangling` | safe name generation | name mangling for `go`
| +| `netutils` | networking utilities | host, port from address
| +| `stringutils` | `string` utilities | search in slice (with case-insensitive)
split/join query parameters as arrays
| +| `typeutils` | `go` types utilities | check the zero value for any type
safe check for a nil value
| +| `yamlutils` | YAML utilities | converting YAML to JSON
loading YAML into a dynamic YAML document
maintaining the original order of keys in YAML objects
require `./jsonutils`
~require `github.com/mailru/easyjson`~
require `go.yaml.in/yaml/v3`
| -* Module `conv` +--- - * [x] convert between values and pointers for any types - * [x] convert from string to builtin types (wraps `strconv`) - * [x] require `./typeutils` (test dependency) +## Dependencies + +The root module `github.com/go-openapi/swag` at the repo level maintains a few +dependencies outside of the standard library. -* Module `fileutils` +* YAML utilities depend on `go.yaml.in/yaml/v3` +* JSON utilities depend on their registered adapter module: + * by default, only the standard library is used + * `github.com/mailru/easyjson` is now only a dependency for module + `github.com/go-openapi/swag/jsonutils/adapters/easyjson/json`, + for users willing to import that module. + * integration tests and benchmarks use all the dependencies are published as their own module +* other dependencies are test dependencies drawn from `github.com/stretchr/testify` - * [x] file upload type - * [x] search in path (deprecated) +## Release notes -* Module `jsonname` +### v0.25.4 - * [x] infer JSON names from go properties +** mangling** -* Module `jsonutils` +Bug fix - * [x] fast json concatenation - * [x] read and write JSON from and to dynamic go data structures - * [x] require `github.com/mailru/easyjson` +* [x] mangler may panic with pluralized overlapping initialisms -* Module `loading` +Tests - * [x] load from file or http - * [x] require `./yamlutils` +* [x] introduced fuzz tests -* Module `mangling` +### v0.25.3 - * [x] name mangling for go +** mangling** -* Module `netutils` +Bug fix - * [x] host, port from address +* [x] mangler may panic with pluralized initialisms -* Module `stringutils` +### v0.25.2 - * [x] search in slice (with case-insensitive) - * [x] split/join query parameters as arrays +Minor changes due to internal maintenance that don't affect the behavior of the library. -* Module `typeutils` +* [x] removed indirect test dependencies by switching all tests to `go-openapi/testify`, + a fork of `stretch/testify` with zero-dependencies. +* [x] improvements to CI to catch test reports. +* [x] modernized licensing annotations in source code, using the more compact SPDX annotations + rather than the full license terms. +* [x] simplified a bit JSON & YAML testing by using newly available assertions +* started the journey to an OpenSSF score card badge: + * [x] explicited permissions in CI workflows + * [x] published security policy + * pinned dependencies to github actions + * introduced fuzzing in tests - * [x] check the zero value for any type +### v0.25.1 -* Module `yamlutils` +* fixes a data race that could occur when using the standard library implementation of a JSON ordered map - * [x] converting YAML to JSON - * [x] loading YAML into a dynamic YAML document - * [x] require `./jsonutils` - * [x] require `github.com/mailru/easyjson` - * [x] require `gopkg.in/yaml.v3` +### v0.25.0 ---- +**New with this release**: -The root module `github.com/go-openapi/swag` at the repo level maintains a few -dependencies outside of the standard library: +* requires `go1.24`, as iterators are being introduced +* removes the dependency to `mailru/easyjson` by default (#68) + * functionality remains the same, but performance may somewhat degrade for applications + that relied on `easyjson` + * users of the JSON or YAML utilities who want to use `easyjson` as their preferred JSON serializer library + will be able to do so by registering this the corresponding JSON adapter at runtime. See below. + * ordered keys in JSON and YAML objects: this feature used to rely solely on `easyjson`. + With this release, an implementation relying on the standard `encoding/json` is provided. + * an independent [benchmark](./jsonutils/adapters/testintegration/benchmarks/README.md) to compare the different adapters +* improves the "float is integer" check (`conv.IsFloat64AJSONInteger`) (#59) +* removes the _direct_ dependency to `gopkg.in/yaml.v3` (indirect dependency is still incurred through `stretchr/testify`) (#127) +* exposed `conv.IsNil()` (previously kept private): a safe nil check (accounting for the "non-nil interface with nil value" nonsensical go trick) -* YAML utilities depend on `gopkg.in/yaml.v3` -* JSON utilities `github.com/mailru/easyjson` +**What coming next?** -This is not necessarily the case for all sub-modules. +Moving forward, we want to : +* provide an implementation of the JSON adapter based on `encoding/json/v2`, for `go1.25` builds. +* provide similar implementations for `goccy/go-json` and `jsoniterator/go`, and perhaps some other + similar libraries may be interesting too. -## Release notes -### v0.25.0 [draft, unreleased] +**How to explicitly register a dependency at runtime**? -* v0.25.0 will remove the dependency to `mailru/easyjson` by default. -* users of JSON or YAML utility who want to use `easyjson` as their - prefered JSON marshaler will be able to do so by registering it - at runtime. +The following would maintain how JSON utilities proposed by `swag` used work, up to `v0.24.1`. + + ```go + import ( + "github.com/go-openapi/swag/jsonutils/adapters" + easyjson "github.com/go-openapi/swag/jsonutils/adapters/easyjson/json" + ) + + func init() { + easyjson.Register(adapters.Registry) + } + ``` + +Subsequent calls to `jsonutils.ReadJSON()` or `jsonutils.WriteJSON()` will switch to `easyjson` +whenever the passed data structures implement the `easyjson.Unmarshaler` or `easyjson.Marshaler` respectively, +or fallback to the standard library. + +For more details, you may also look at our +[integration tests](jsonutils/adapters/testintegration/integration_suite_test.go#29). ### v0.24.0 @@ -121,14 +176,13 @@ With this release, we have largely modernized the API of `swag`: --- -Moving forward, no additional feature will be added to the `swag` API directly. - -However, child modules will continue to evolve or some new ones may be added in the future. +## Licensing +This library ships under the [SPDX-License-Identifier: Apache-2.0](./LICENSE). -#### Note to contributors +## Note to contributors -The mono-repo structure comes with some unavoidable extra pains... +A mono-repo structure comes with some unavoidable extra pains... * Testing @@ -153,7 +207,14 @@ The mono-repo structure comes with some unavoidable extra pains... > We'd like to adopt the rule that modules in this repo would only differ by a patch version > (e.g. `v0.24.5` vs `v0.24.3`), and we'll level all modules whenever a minor version is introduced. > -> A script in `./hack` is provided to tag all modules in one go at the same level in one go. +> A script in `./hack` is provided to tag all modules with the same version in one go. + +* Continuous integration + +> At this moment, all tests in all modules are systematically run over the full test matrix (3 platform x 2 go releases). +> This generates quite a lot of jobs. +> +> We ought to reduce the number of jobs required to test a PR focused on only a few modules. ## Todos, suggestions and plans @@ -161,11 +222,18 @@ All kinds of contributions are welcome. A few ideas: -* [ ] Complete the split of dependencies to isolate easyjson from the rest +* [x] Complete the split of dependencies to isolate easyjson from the rest +* [x] Improve CI to reduce needed tests +* [x] Replace dependency to `gopkg.in/yaml.v3` (`yamlutil`) * [ ] Improve mangling utilities (improve readability, support for capitalized words, better word substitution for non-letter symbols...) * [ ] Move back to this common shared pot a few of the technical features introduced by go-swagger independently (e.g. mangle go package names, search package with go modules support, ...) * [ ] Apply a similar mono-repo approach to go-openapi/strfmt which suffer from similar woes: bloated API, imposed dependency to some database driver. - +* [ ] Adapt `go-swagger` (incl. generated code) to the new `swag` API. +* [ ] Factorize some tests, as there is a lot of redundant testing code in `jsonutils` +* [ ] Benchmark & profiling: publish independently the tool built to analyze and chart benchmarks (e.g. similar to `benchvisual`) +* [ ] more thorough testing for nil / null case +* [ ] ci pipeline to manage releases +* [ ] cleaner mockery generation (doesn't work out of the box for all sub-modules) diff --git a/vendor/github.com/go-openapi/swag/SECURITY.md b/vendor/github.com/go-openapi/swag/SECURITY.md new file mode 100644 index 000000000..72296a831 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/SECURITY.md @@ -0,0 +1,19 @@ +# Security Policy + +This policy outlines the commitment and practices of the go-openapi maintainers regarding security. + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 0.25.x | :white_check_mark: | + +## Reporting a vulnerability + +If you become aware of a security vulnerability that affects the current repository, +please report it privately to the maintainers. + +Please follow the instructions provided by github to +[Privately report a security vulnerability](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability). + +TL;DR: on Github, navigate to the project's "Security" tab then click on "Report a vulnerability". diff --git a/vendor/github.com/go-openapi/swag/cmdutils/cmd_utils.go b/vendor/github.com/go-openapi/swag/cmdutils/cmd_utils.go index bc01ec2bb..6c7bbb26f 100644 --- a/vendor/github.com/go-openapi/swag/cmdutils/cmd_utils.go +++ b/vendor/github.com/go-openapi/swag/cmdutils/cmd_utils.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package cmdutils @@ -20,5 +9,5 @@ package cmdutils type CommandLineOptionsGroup struct { ShortDescription string LongDescription string - Options interface{} + Options any } diff --git a/vendor/github.com/go-openapi/swag/cmdutils/doc.go b/vendor/github.com/go-openapi/swag/cmdutils/doc.go index 63ac1d17e..31f2c3753 100644 --- a/vendor/github.com/go-openapi/swag/cmdutils/doc.go +++ b/vendor/github.com/go-openapi/swag/cmdutils/doc.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 // Package cmdutils brings helpers for CLIs produced by go-openapi package cmdutils diff --git a/vendor/github.com/go-openapi/swag/cmdutils_iface.go b/vendor/github.com/go-openapi/swag/cmdutils_iface.go index 1eaf36f15..bd0c1fc12 100644 --- a/vendor/github.com/go-openapi/swag/cmdutils_iface.go +++ b/vendor/github.com/go-openapi/swag/cmdutils_iface.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package swag diff --git a/vendor/github.com/go-openapi/swag/conv/convert.go b/vendor/github.com/go-openapi/swag/conv/convert.go index b9b869854..f205c3913 100644 --- a/vendor/github.com/go-openapi/swag/conv/convert.go +++ b/vendor/github.com/go-openapi/swag/conv/convert.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package conv diff --git a/vendor/github.com/go-openapi/swag/conv/convert_types.go b/vendor/github.com/go-openapi/swag/conv/convert_types.go index 423e8663f..cf4c6495e 100644 --- a/vendor/github.com/go-openapi/swag/conv/convert_types.go +++ b/vendor/github.com/go-openapi/swag/conv/convert_types.go @@ -1,20 +1,13 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package conv -// The original version of this file, eons ago, was taken from the aws go sdk +// Unlicensed credits (idea, concept) +// +// The idea to convert values to pointers and the other way around, was inspired, eons ago, by the aws go sdk. +// +// Nowadays, all sensible API sdk's expose a similar functionality. // Pointer returns a pointer to the value passed in. func Pointer[T any](v T) *T { diff --git a/vendor/github.com/go-openapi/swag/conv/doc.go b/vendor/github.com/go-openapi/swag/conv/doc.go index b02711f42..1bd6ead6e 100644 --- a/vendor/github.com/go-openapi/swag/conv/doc.go +++ b/vendor/github.com/go-openapi/swag/conv/doc.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 // Package conv exposes utilities to convert types. // diff --git a/vendor/github.com/go-openapi/swag/conv/format.go b/vendor/github.com/go-openapi/swag/conv/format.go index db7562a4a..5b87b8e14 100644 --- a/vendor/github.com/go-openapi/swag/conv/format.go +++ b/vendor/github.com/go-openapi/swag/conv/format.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package conv diff --git a/vendor/github.com/go-openapi/swag/conv/sizeof.go b/vendor/github.com/go-openapi/swag/conv/sizeof.go index 646f8be9a..494346557 100644 --- a/vendor/github.com/go-openapi/swag/conv/sizeof.go +++ b/vendor/github.com/go-openapi/swag/conv/sizeof.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + package conv import "unsafe" diff --git a/vendor/github.com/go-openapi/swag/conv/type_constraints.go b/vendor/github.com/go-openapi/swag/conv/type_constraints.go index 3c6149836..81135e827 100644 --- a/vendor/github.com/go-openapi/swag/conv/type_constraints.go +++ b/vendor/github.com/go-openapi/swag/conv/type_constraints.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package conv diff --git a/vendor/github.com/go-openapi/swag/conv_iface.go b/vendor/github.com/go-openapi/swag/conv_iface.go index 9991acb65..eea7b2e56 100644 --- a/vendor/github.com/go-openapi/swag/conv_iface.go +++ b/vendor/github.com/go-openapi/swag/conv_iface.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package swag diff --git a/vendor/github.com/go-openapi/swag/doc.go b/vendor/github.com/go-openapi/swag/doc.go index b5af82982..b54b57478 100644 --- a/vendor/github.com/go-openapi/swag/doc.go +++ b/vendor/github.com/go-openapi/swag/doc.go @@ -1,78 +1,47 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 // Package swag contains a bunch of helper functions for go-openapi and go-swagger projects. // // You may also use it standalone for your projects. // -// NOTE: all features that were previously exposed as package-level members (constants, variables, +// NOTE: all features that used to be exposed as package-level members (constants, variables, // functions and types) are now deprecated and are superseded by equivalent features in // more specialized sub-packages. +// Moving forward, no additional feature will be added to the [swag] API directly at the root package level, +// which remains there for backward-compatibility purposes. // -// Here is what is inside: -// -// Module [cmdutils]: -// -// - utilities to work with CLIs -// -// Module [conv]: -// -// - convert between value and pointers for builtin types -// - convert from string to builtin types (wraps strconv) +// Child modules will continue to evolve or some new ones may be added in the future. // -// Module [fileutils]: +// # Modules // -// - file upload type -// - search in path +// - [cmdutils] utilities to work with CLIs // -// Module [jsonname]: +// - [conv] type conversion utilities // -// - json names for go properties +// - [fileutils] file utilities // -// Module [jsonutils]: +// - [jsonname] JSON utilities // -// - fast json concatenation -// - read and write JSON from and to dynamic go data structures +// - [jsonutils] JSON utilities // -// Module [loading]: +// - [loading] file loading // -// - load from file or http +// - [mangling] safe name generation // -// Module [mangling]: +// - [netutils] networking utilities // -// - name mangling to generate clean identifiers +// - [stringutils] `string` utilities // -// Module [netutils]: +// - [typeutils] `go` types utilities // -// - host, port from address +// - [yamlutils] YAML utilities // -// Module [stringutils]: -// -// - find string in list -// - join/split arrays of query parameters -// -// Module [typeutils]: -// -// - check the zero value of any type -// -// Module [yamlutils]: -// -// - converting YAML to JSON -// - loading YAML into a dynamic YAML document +// # Dependencies // // This repo has a few dependencies outside of the standard library: // -// - YAML utilities depend on [gopkg.in/yaml.v3] -// - JSON utilities depend on [github.com/mailru/easyjson] +// - YAML utilities depend on [go.yaml.in/yaml/v3] package swag + +//go:generate mockery diff --git a/vendor/github.com/go-openapi/swag/fileutils/doc.go b/vendor/github.com/go-openapi/swag/fileutils/doc.go index 4b48e7196..859a200d8 100644 --- a/vendor/github.com/go-openapi/swag/fileutils/doc.go +++ b/vendor/github.com/go-openapi/swag/fileutils/doc.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 // Package fileutils exposes utilities to deal with files and paths. // diff --git a/vendor/github.com/go-openapi/swag/fileutils/file.go b/vendor/github.com/go-openapi/swag/fileutils/file.go index b17eaba58..5ad4cfaea 100644 --- a/vendor/github.com/go-openapi/swag/fileutils/file.go +++ b/vendor/github.com/go-openapi/swag/fileutils/file.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package fileutils diff --git a/vendor/github.com/go-openapi/swag/fileutils/path.go b/vendor/github.com/go-openapi/swag/fileutils/path.go index a70ccb418..dd09f690b 100644 --- a/vendor/github.com/go-openapi/swag/fileutils/path.go +++ b/vendor/github.com/go-openapi/swag/fileutils/path.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package fileutils @@ -38,11 +27,17 @@ func FindInSearchPath(searchPath, pkg string) string { } // FindInGoSearchPath finds a package in the $GOPATH:$GOROOT +// +// Deprecated: this function is no longer relevant with modern go. +// It uses [runtime.GOROOT] under the hood, which is deprecated as of go1.24. func FindInGoSearchPath(pkg string) string { return FindInSearchPath(FullGoSearchPath(), pkg) } // FullGoSearchPath gets the search paths for finding packages +// +// Deprecated: this function is no longer relevant with modern go. +// It uses [runtime.GOROOT] under the hood, which is deprecated as of go1.24. func FullGoSearchPath() string { allPaths := os.Getenv(GOPATHKey) if allPaths == "" { diff --git a/vendor/github.com/go-openapi/swag/fileutils_iface.go b/vendor/github.com/go-openapi/swag/fileutils_iface.go index 0c639e8c1..f3e79a0e4 100644 --- a/vendor/github.com/go-openapi/swag/fileutils_iface.go +++ b/vendor/github.com/go-openapi/swag/fileutils_iface.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package swag diff --git a/vendor/github.com/go-openapi/swag/go.work b/vendor/github.com/go-openapi/swag/go.work new file mode 100644 index 000000000..1e537f074 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/go.work @@ -0,0 +1,20 @@ +use ( + . + ./cmdutils + ./conv + ./fileutils + ./jsonname + ./jsonutils + ./jsonutils/adapters/easyjson + ./jsonutils/adapters/testintegration + ./jsonutils/adapters/testintegration/benchmarks + ./jsonutils/fixtures_test + ./loading + ./mangling + ./netutils + ./stringutils + ./typeutils + ./yamlutils +) + +go 1.24.0 diff --git a/vendor/github.com/go-openapi/swag/go.work.sum b/vendor/github.com/go-openapi/swag/go.work.sum new file mode 100644 index 000000000..c1308cafa --- /dev/null +++ b/vendor/github.com/go-openapi/swag/go.work.sum @@ -0,0 +1,7 @@ +github.com/go-openapi/testify/v2 v2.0.1/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= diff --git a/vendor/github.com/go-openapi/swag/jsonname/doc.go b/vendor/github.com/go-openapi/swag/jsonname/doc.go index b2e0c80fc..79232eaca 100644 --- a/vendor/github.com/go-openapi/swag/jsonname/doc.go +++ b/vendor/github.com/go-openapi/swag/jsonname/doc.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 // Package jsonname is a provider of json property names from go properties. package jsonname diff --git a/vendor/github.com/go-openapi/swag/jsonname/name_provider.go b/vendor/github.com/go-openapi/swag/jsonname/name_provider.go index e87aac2f7..8eaf1bece 100644 --- a/vendor/github.com/go-openapi/swag/jsonname/name_provider.go +++ b/vendor/github.com/go-openapi/swag/jsonname/name_provider.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package jsonname @@ -90,7 +79,7 @@ func newNameIndex(tpe reflect.Type) nameIndex { } // GetJSONNames gets all the json property names for a type -func (n *NameProvider) GetJSONNames(subject interface{}) []string { +func (n *NameProvider) GetJSONNames(subject any) []string { n.lock.Lock() defer n.lock.Unlock() tpe := reflect.Indirect(reflect.ValueOf(subject)).Type() @@ -107,7 +96,7 @@ func (n *NameProvider) GetJSONNames(subject interface{}) []string { } // GetJSONName gets the json name for a go property name -func (n *NameProvider) GetJSONName(subject interface{}, name string) (string, bool) { +func (n *NameProvider) GetJSONName(subject any, name string) (string, bool) { tpe := reflect.Indirect(reflect.ValueOf(subject)).Type() return n.GetJSONNameForType(tpe, name) } @@ -125,7 +114,7 @@ func (n *NameProvider) GetJSONNameForType(tpe reflect.Type, name string) (string } // GetGoName gets the go name for a json property name -func (n *NameProvider) GetGoName(subject interface{}, name string) (string, bool) { +func (n *NameProvider) GetGoName(subject any, name string) (string, bool) { tpe := reflect.Indirect(reflect.ValueOf(subject)).Type() return n.GetGoNameForType(tpe, name) } diff --git a/vendor/github.com/go-openapi/swag/jsonname_iface.go b/vendor/github.com/go-openapi/swag/jsonname_iface.go index 555369d75..303a007f6 100644 --- a/vendor/github.com/go-openapi/swag/jsonname_iface.go +++ b/vendor/github.com/go-openapi/swag/jsonname_iface.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package swag diff --git a/vendor/github.com/go-openapi/swag/jsonutils/README.md b/vendor/github.com/go-openapi/swag/jsonutils/README.md new file mode 100644 index 000000000..d745cdb46 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/jsonutils/README.md @@ -0,0 +1,108 @@ + # jsonutils + +`jsonutils` exposes a few tools to work with JSON: + +- a fast, simple `Concat` to concatenate (not merge) JSON objects and arrays +- `FromDynamicJSON` to convert a data structure into a "dynamic JSON" data structure +- `ReadJSON` and `WriteJSON` behave like `json.Unmarshal` and `json.Marshal`, + with the ability to use another underlying serialization library through an `Adapter` + configured at runtime +- a `JSONMapSlice` structure that may be used to store JSON objects with the order of keys maintained + +## Dynamic JSON + +We call "dynamic JSON" the go data structure that results from unmarshaling JSON like this: + +```go + var value any + jsonBytes := `{"a": 1, ... }` + _ = json.Unmarshal(jsonBytes, &value) +``` + +In this configuration, the standard library mappings are as follows: + +| JSON | go | +|-----------|------------------| +| `number` | `float64` | +| `string` | `string` | +| `boolean` | `bool` | +| `null` | `nil` | +| `object` | `map[string]any` | +| `array` | `[]any` | + +## Map slices + +When using `JSONMapSlice`, the ordering of keys is ensured by replacing +mappings to `map[string]any` by a `JSONMapSlice` which is an (ordered) +slice of `JSONMapItem`s. + +Notice that a similar feature is available for YAML (see [`yamlutils`](../yamlutils)), +with a `YAMLMapSlice` type based on the `JSONMapSlice`. + +`JSONMapSlice` is similar to an ordered map, but the keys are not retrieved +in constant time. + +Another difference with the the above standard mappings is that numbers don't always map +to a `float64`: if the value is a JSON integer, it unmarshals to `int64`. + +See also [some examples](https://pkg.go.dev/github.com/go-openapi/swag/jsonutils#pkg-examples) + +## Adapters + +`ReadJSON`, `WriteJSON` and `FromDynamicJSON` (which is a combination of the latter two) +are wrappers on top of `json.Unmarshal` and `json.Marshal`. + +By default, the adapter merely wraps the standard library. + +The adapter may be used to register other JSON serialization libraries, +possibly several ones at the same time. + +If the value passed is identified as an "ordered map" (i.e. implements `ifaces.Ordered` +or `ifaces.SetOrdered`, the adapter favors the "ordered" JSON behavior and tries to +find a registered implementation that support ordered keys in objects. + +Our standard library implementation supports this. + +As of `v0.25.0`, we support through such an adapter the popular `mailru/easyjson` +library, which kicks in when the passed values support the `easyjson.Unmarshaler` +or `easyjson.Marshaler` interfaces. + +In the future, we plan to add more similar libraries that compete on the go JSON +serializers scene. + +## Registering an adapter + +In package `github.com/go-openapi/swag/easyjson/adapters`, several adapters are available. + +Each adapter is an independent go module. Hence you'll pick its dependencies only if you import it. + +At this moment we provide: +* `stdlib`: JSON adapter based on the standard library +* `easyjson`: JSON adapter based on the `github.com/mailru/easyjson` + +The adapters provide the basic `Marshal` and `Unmarshal` capabilities, plus an implementation +of the `MapSlice` pattern. + +You may also build your own adapter based on your specific use-case. An adapter is not required to implement +all capabilities. + +Every adapter comes with a `Register` function, possibly with some options, to register the adapter +to a global registry. + +For example, to enable `easyjson` to be used in `ReadJSON` and `WriteJSON`, you would write something like: + +```go + import ( + "github.com/go-openapi/swag/jsonutils/adapters" + easyjson "github.com/go-openapi/swag/jsonutils/adapters/easyjson/json" + ) + + func init() { + easyjson.Register(adapters.Registry) + } +``` + +You may register several adapters. In this case, capability matching is evaluated from the last registered +adapters (LIFO). + +## [Benchmarks](./adapters/testintegration/benchmarks/README.md) diff --git a/vendor/github.com/go-openapi/swag/jsonutils/adapters/doc.go b/vendor/github.com/go-openapi/swag/jsonutils/adapters/doc.go new file mode 100644 index 000000000..76d3898fc --- /dev/null +++ b/vendor/github.com/go-openapi/swag/jsonutils/adapters/doc.go @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +// Package adapters exposes a registry of adapters to multiple +// JSON serialization libraries. +// +// All interfaces are defined in package [ifaces.Adapter]. +package adapters diff --git a/vendor/github.com/go-openapi/swag/jsonutils/adapters/ifaces/doc.go b/vendor/github.com/go-openapi/swag/jsonutils/adapters/ifaces/doc.go new file mode 100644 index 000000000..1fd43a1fa --- /dev/null +++ b/vendor/github.com/go-openapi/swag/jsonutils/adapters/ifaces/doc.go @@ -0,0 +1,5 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +// Package ifaces exposes all interfaces to work with adapters. +package ifaces diff --git a/vendor/github.com/go-openapi/swag/jsonutils/adapters/ifaces/ifaces.go b/vendor/github.com/go-openapi/swag/jsonutils/adapters/ifaces/ifaces.go new file mode 100644 index 000000000..7805e5e5e --- /dev/null +++ b/vendor/github.com/go-openapi/swag/jsonutils/adapters/ifaces/ifaces.go @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package ifaces + +import ( + _ "encoding/json" // for documentation purpose + "iter" +) + +// Ordered knows how to iterate over the (key,value) pairs of a JSON object. +type Ordered interface { + OrderedItems() iter.Seq2[string, any] +} + +// SetOrdered knows how to append or update the keys of a JSON object, +// given an iterator over (key,value) pairs. +// +// If the provided iterator is nil then the receiver should be set to nil. +type SetOrdered interface { + SetOrderedItems(iter.Seq2[string, any]) +} + +// OrderedMap represent a JSON object (i.e. like a map[string,any]), +// and knows how to serialize and deserialize JSON with the order of keys maintained. +type OrderedMap interface { + Ordered + SetOrdered + + OrderedMarshalJSON() ([]byte, error) + OrderedUnmarshalJSON([]byte) error +} + +// MarshalAdapter behaves likes the standard library [json.Marshal]. +type MarshalAdapter interface { + Poolable + + Marshal(any) ([]byte, error) +} + +// OrderedMarshalAdapter behaves likes the standard library [json.Marshal], preserving the order of keys in objects. +type OrderedMarshalAdapter interface { + Poolable + + OrderedMarshal(Ordered) ([]byte, error) +} + +// UnmarshalAdapter behaves likes the standard library [json.Unmarshal]. +type UnmarshalAdapter interface { + Poolable + + Unmarshal([]byte, any) error +} + +// OrderedUnmarshalAdapter behaves likes the standard library [json.Unmarshal], preserving the order of keys in objects. +type OrderedUnmarshalAdapter interface { + Poolable + + OrderedUnmarshal([]byte, SetOrdered) error +} + +// Adapter exposes an interface like the standard [json] library. +type Adapter interface { + MarshalAdapter + UnmarshalAdapter + + OrderedAdapter +} + +// OrderedAdapter exposes interfaces to process JSON and keep the order of object keys. +type OrderedAdapter interface { + OrderedMarshalAdapter + OrderedUnmarshalAdapter + NewOrderedMap(capacity int) OrderedMap +} + +type Poolable interface { + // Self-redeem: for [Adapter] s that are allocated from a pool. + // The [Adapter] must not be used after calling [Redeem]. + Redeem() + + // Reset the state of the [Adapter], if any. + Reset() +} diff --git a/vendor/github.com/go-openapi/swag/jsonutils/adapters/ifaces/registry_iface.go b/vendor/github.com/go-openapi/swag/jsonutils/adapters/ifaces/registry_iface.go new file mode 100644 index 000000000..2d6c69f4e --- /dev/null +++ b/vendor/github.com/go-openapi/swag/jsonutils/adapters/ifaces/registry_iface.go @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package ifaces + +import ( + "strings" +) + +// Capability indicates what a JSON adapter is capable of. +type Capability uint8 + +const ( + CapabilityMarshalJSON Capability = 1 << iota + CapabilityUnmarshalJSON + CapabilityOrderedMarshalJSON + CapabilityOrderedUnmarshalJSON + CapabilityOrderedMap +) + +func (c Capability) String() string { + switch c { + case CapabilityMarshalJSON: + return "MarshalJSON" + case CapabilityUnmarshalJSON: + return "UnmarshalJSON" + case CapabilityOrderedMarshalJSON: + return "OrderedMarshalJSON" + case CapabilityOrderedUnmarshalJSON: + return "OrderedUnmarshalJSON" + case CapabilityOrderedMap: + return "OrderedMap" + default: + return "" + } +} + +// Capabilities holds several unitary capability flags +type Capabilities uint8 + +// Has some capability flag enabled. +func (c Capabilities) Has(capability Capability) bool { + return Capability(c)&capability > 0 +} + +func (c Capabilities) String() string { + var w strings.Builder + + first := true + for _, capability := range []Capability{ + CapabilityMarshalJSON, + CapabilityUnmarshalJSON, + CapabilityOrderedMarshalJSON, + CapabilityOrderedUnmarshalJSON, + CapabilityOrderedMap, + } { + if c.Has(capability) { + if !first { + w.WriteByte('|') + } else { + first = false + } + w.WriteString(capability.String()) + } + } + + return w.String() +} + +const ( + AllCapabilities Capabilities = Capabilities(uint8(CapabilityMarshalJSON) | + uint8(CapabilityUnmarshalJSON) | + uint8(CapabilityOrderedMarshalJSON) | + uint8(CapabilityOrderedUnmarshalJSON) | + uint8(CapabilityOrderedMap)) + + AllUnorderedCapabilities Capabilities = Capabilities(uint8(CapabilityMarshalJSON) | uint8(CapabilityUnmarshalJSON)) +) + +// RegistryEntry describes how any given adapter registers its capabilities to the [Registrar]. +type RegistryEntry struct { + Who string + What Capabilities + Constructor func() Adapter + Support func(what Capability, value any) bool +} + +// Registrar is a type that knows how to keep registration calls from adapters. +type Registrar interface { + RegisterFor(RegistryEntry) +} diff --git a/vendor/github.com/go-openapi/swag/jsonutils/adapters/registry.go b/vendor/github.com/go-openapi/swag/jsonutils/adapters/registry.go new file mode 100644 index 000000000..3062acaff --- /dev/null +++ b/vendor/github.com/go-openapi/swag/jsonutils/adapters/registry.go @@ -0,0 +1,229 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package adapters + +import ( + "fmt" + "reflect" + "slices" + "sync" + + "github.com/go-openapi/swag/jsonutils/adapters/ifaces" + stdlib "github.com/go-openapi/swag/jsonutils/adapters/stdlib/json" +) + +// Registry holds the global registry for registered adapters. +var Registry = NewRegistrar() + +var ( + defaultRegistered = stdlib.Register + + _ ifaces.Registrar = &Registrar{} +) + +type registryError string + +func (e registryError) Error() string { + return string(e) +} + +// ErrRegistry indicates an error returned by the [Registrar]. +var ErrRegistry registryError = "JSON adapters registry error" + +type registry []*ifaces.RegistryEntry + +// Registrar holds registered [ifaces.Adapters] for different serialization capabilities. +// +// Internally, it maintains a cache for data types that favor a given adapter. +type Registrar struct { + marshalerRegistry registry + unmarshalerRegistry registry + orderedMarshalerRegistry registry + orderedUnmarshalerRegistry registry + orderedMapRegistry registry + + gmx sync.RWMutex + + // cache indexed by value type, so we don't have to lookup + marshalerCache map[reflect.Type]*ifaces.RegistryEntry + unmarshalerCache map[reflect.Type]*ifaces.RegistryEntry + orderedMarshalerCache map[reflect.Type]*ifaces.RegistryEntry + orderedUnmarshalerCache map[reflect.Type]*ifaces.RegistryEntry + orderedMapCache map[reflect.Type]*ifaces.RegistryEntry +} + +func NewRegistrar() *Registrar { + r := &Registrar{} + + r.marshalerRegistry = make(registry, 0, 1) + r.unmarshalerRegistry = make(registry, 0, 1) + r.orderedMarshalerRegistry = make(registry, 0, 1) + r.orderedUnmarshalerRegistry = make(registry, 0, 1) + r.orderedMapRegistry = make(registry, 0, 1) + + r.marshalerCache = make(map[reflect.Type]*ifaces.RegistryEntry) + r.unmarshalerCache = make(map[reflect.Type]*ifaces.RegistryEntry) + r.orderedMarshalerCache = make(map[reflect.Type]*ifaces.RegistryEntry) + r.orderedUnmarshalerCache = make(map[reflect.Type]*ifaces.RegistryEntry) + r.orderedMapCache = make(map[reflect.Type]*ifaces.RegistryEntry) + + defaultRegistered(r) + + return r +} + +// ClearCache resets the internal type cache. +func (r *Registrar) ClearCache() { + r.gmx.Lock() + r.clearCache() + r.gmx.Unlock() +} + +// Reset the [Registrar] to its defaults. +func (r *Registrar) Reset() { + r.gmx.Lock() + r.clearCache() + r.marshalerRegistry = r.marshalerRegistry[:0] + r.unmarshalerRegistry = r.unmarshalerRegistry[:0] + r.orderedMarshalerRegistry = r.orderedMarshalerRegistry[:0] + r.orderedUnmarshalerRegistry = r.orderedUnmarshalerRegistry[:0] + r.orderedMapRegistry = r.orderedMapRegistry[:0] + r.gmx.Unlock() + + defaultRegistered(r) +} + +// RegisterFor registers an adapter for some JSON capabilities. +func (r *Registrar) RegisterFor(entry ifaces.RegistryEntry) { + r.gmx.Lock() + if entry.What.Has(ifaces.CapabilityMarshalJSON) { + e := entry + e.What &= ifaces.Capabilities(ifaces.CapabilityMarshalJSON) + r.marshalerRegistry = slices.Insert(r.marshalerRegistry, 0, &e) + } + if entry.What.Has(ifaces.CapabilityUnmarshalJSON) { + e := entry + e.What &= ifaces.Capabilities(ifaces.CapabilityUnmarshalJSON) + r.unmarshalerRegistry = slices.Insert(r.unmarshalerRegistry, 0, &e) + } + if entry.What.Has(ifaces.CapabilityOrderedMarshalJSON) { + e := entry + e.What &= ifaces.Capabilities(ifaces.CapabilityOrderedMarshalJSON) + r.orderedMarshalerRegistry = slices.Insert(r.orderedMarshalerRegistry, 0, &e) + } + if entry.What.Has(ifaces.CapabilityOrderedUnmarshalJSON) { + e := entry + e.What &= ifaces.Capabilities(ifaces.CapabilityOrderedUnmarshalJSON) + r.orderedUnmarshalerRegistry = slices.Insert(r.orderedUnmarshalerRegistry, 0, &e) + } + if entry.What.Has(ifaces.CapabilityOrderedMap) { + e := entry + e.What &= ifaces.Capabilities(ifaces.CapabilityOrderedMap) + r.orderedMapRegistry = slices.Insert(r.orderedMapRegistry, 0, &e) + } + r.gmx.Unlock() +} + +// AdapterFor returns an [ifaces.Adapter] that supports this capability for this type of value. +// +// The [ifaces.Adapter] may be redeemed to its pool using its Redeem() method, for adapters that support global +// pooling. When this is not the case, the redeem function is just a no-operation. +func (r *Registrar) AdapterFor(capability ifaces.Capability, value any) ifaces.Adapter { + entry := r.findFirstFor(capability, value) + if entry == nil { + return nil + } + + return entry.Constructor() +} + +func (r *Registrar) clearCache() { + clear(r.marshalerCache) + clear(r.unmarshalerCache) + clear(r.orderedMarshalerCache) + clear(r.orderedUnmarshalerCache) + clear(r.orderedMapCache) +} + +func (r *Registrar) findFirstFor(capability ifaces.Capability, value any) *ifaces.RegistryEntry { + switch capability { + case ifaces.CapabilityMarshalJSON: + return r.findFirstInRegistryFor(r.marshalerRegistry, r.marshalerCache, capability, value) + case ifaces.CapabilityUnmarshalJSON: + return r.findFirstInRegistryFor(r.unmarshalerRegistry, r.unmarshalerCache, capability, value) + case ifaces.CapabilityOrderedMarshalJSON: + return r.findFirstInRegistryFor(r.orderedMarshalerRegistry, r.orderedMarshalerCache, capability, value) + case ifaces.CapabilityOrderedUnmarshalJSON: + return r.findFirstInRegistryFor(r.orderedUnmarshalerRegistry, r.orderedUnmarshalerCache, capability, value) + case ifaces.CapabilityOrderedMap: + return r.findFirstInRegistryFor(r.orderedMapRegistry, r.orderedMapCache, capability, value) + default: + panic(fmt.Errorf("unsupported capability %d: %w", capability, ErrRegistry)) + } +} + +func (r *Registrar) findFirstInRegistryFor(reg registry, cache map[reflect.Type]*ifaces.RegistryEntry, capability ifaces.Capability, value any) *ifaces.RegistryEntry { + r.gmx.RLock() + if len(reg) > 1 { + if entry, ok := cache[reflect.TypeOf(value)]; ok { + // cache hit + r.gmx.RUnlock() + return entry + } + } + + for _, entry := range reg { + if !entry.Support(capability, value) { + continue + } + + r.gmx.RUnlock() + + // update the internal cache + r.gmx.Lock() + cache[reflect.TypeOf(value)] = entry + r.gmx.Unlock() + + return entry + } + + // no adapter found + r.gmx.RUnlock() + + return nil +} + +// MarshalAdapterFor returns the first adapter that knows how to Marshal this type of value. +func MarshalAdapterFor(value any) ifaces.MarshalAdapter { + return Registry.AdapterFor(ifaces.CapabilityMarshalJSON, value) +} + +// OrderedMarshalAdapterFor returns the first adapter that knows how to OrderedMarshal this type of value. +func OrderedMarshalAdapterFor(value ifaces.Ordered) ifaces.OrderedMarshalAdapter { + return Registry.AdapterFor(ifaces.CapabilityOrderedMarshalJSON, value) +} + +// UnmarshalAdapterFor returns the first adapter that knows how to Unmarshal this type of value. +func UnmarshalAdapterFor(value any) ifaces.UnmarshalAdapter { + return Registry.AdapterFor(ifaces.CapabilityUnmarshalJSON, value) +} + +// OrderedUnmarshalAdapterFor provides the first adapter that knows how to OrderedUnmarshal this type of value. +func OrderedUnmarshalAdapterFor(value ifaces.SetOrdered) ifaces.OrderedUnmarshalAdapter { + return Registry.AdapterFor(ifaces.CapabilityOrderedUnmarshalJSON, value) +} + +// NewOrderedMap provides the "ordered map" implementation provided by the registry. +func NewOrderedMap(capacity int) ifaces.OrderedMap { + var v any + adapter := Registry.AdapterFor(ifaces.CapabilityOrderedUnmarshalJSON, v) + if adapter == nil { + return nil + } + + defer adapter.Redeem() + return adapter.NewOrderedMap(capacity) +} + +func noopRedeemer() {} diff --git a/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/adapter.go b/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/adapter.go new file mode 100644 index 000000000..0213ff5c2 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/adapter.go @@ -0,0 +1,115 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package json + +import ( + stdjson "encoding/json" + + "github.com/go-openapi/swag/jsonutils/adapters/ifaces" + "github.com/go-openapi/swag/typeutils" +) + +const sensibleBufferSize = 8192 + +type jsonError string + +func (e jsonError) Error() string { + return string(e) +} + +// ErrStdlib indicates that an error comes from the stdlib JSON adapter +var ErrStdlib jsonError = "error from the JSON adapter stdlib" + +var _ ifaces.Adapter = &Adapter{} + +type Adapter struct { +} + +// NewAdapter yields an [ifaces.Adapter] using the standard library. +func NewAdapter() *Adapter { + return &Adapter{} +} + +func (a *Adapter) Marshal(value any) ([]byte, error) { + return stdjson.Marshal(value) +} + +func (a *Adapter) Unmarshal(data []byte, value any) error { + return stdjson.Unmarshal(data, value) +} + +func (a *Adapter) OrderedMarshal(value ifaces.Ordered) ([]byte, error) { + w := poolOfWriters.Borrow() + defer func() { + poolOfWriters.Redeem(w) + }() + + if typeutils.IsNil(value) { + w.RawString("null") + + return w.BuildBytes() + } + + w.RawByte('{') + first := true + for k, v := range value.OrderedItems() { + if first { + first = false + } else { + w.RawByte(',') + } + + w.String(k) + w.RawByte(':') + + switch val := v.(type) { + case ifaces.Ordered: + w.Raw(a.OrderedMarshal(val)) + default: + w.Raw(stdjson.Marshal(v)) + } + } + + w.RawByte('}') + + return w.BuildBytes() +} + +func (a *Adapter) OrderedUnmarshal(data []byte, value ifaces.SetOrdered) error { + var m MapSlice + if err := m.OrderedUnmarshalJSON(data); err != nil { + return err + } + + if typeutils.IsNil(m) { + // force input value to nil + value.SetOrderedItems(nil) + + return nil + } + + value.SetOrderedItems(m.OrderedItems()) + + return nil +} + +func (a *Adapter) NewOrderedMap(capacity int) ifaces.OrderedMap { + m := make(MapSlice, 0, capacity) + + return &m +} + +// Redeem the [Adapter] when it comes from a pool. +// +// The adapter becomes immediately unusable once redeemed. +func (a *Adapter) Redeem() { + if a == nil { + return + } + + RedeemAdapter(a) +} + +func (a *Adapter) Reset() { +} diff --git a/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/doc.go b/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/doc.go new file mode 100644 index 000000000..5ea1b4404 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/doc.go @@ -0,0 +1,5 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +// Package json implements an [ifaces.Adapter] using the standard library. +package json diff --git a/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/lexer.go b/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/lexer.go new file mode 100644 index 000000000..b5aa1c797 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/lexer.go @@ -0,0 +1,320 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package json + +import ( + stdjson "encoding/json" + "errors" + "fmt" + "io" + "math" + "strconv" + + "github.com/go-openapi/swag/conv" +) + +type token struct { + stdjson.Token +} + +func (t token) String() string { + if t == invalidToken { + return "invalid token" + } + if t == eofToken { + return "EOF" + } + + return fmt.Sprintf("%v", t.Token) +} + +func (t token) Kind() tokenKind { + switch t.Token.(type) { + case nil: + return tokenNull + case stdjson.Delim: + return tokenDelim + case bool: + return tokenBool + case float64: + return tokenFloat + case stdjson.Number: + return tokenNumber + case string: + return tokenString + default: + return tokenUndef + } +} + +func (t token) Delim() byte { + r, ok := t.Token.(stdjson.Delim) + if !ok { + return 0 + } + + return byte(r) +} + +type tokenKind uint8 + +const ( + tokenUndef tokenKind = iota + tokenString + tokenNumber + tokenFloat + tokenBool + tokenNull + tokenDelim +) + +var ( + invalidToken = token{ + Token: stdjson.Token(struct{}{}), + } + + eofToken = token{ + Token: stdjson.Token(&struct{}{}), + } + + undefToken = token{ + Token: stdjson.Token(uint8(0)), + } +) + +// jlexer apes easyjson's jlexer, but uses the standard library decoder under the hood. +type jlexer struct { + buf *bytesReader + dec *stdjson.Decoder + err error + // current token + next token + // started bool +} + +type bytesReader struct { + buf []byte + offset int +} + +func (b *bytesReader) Reset() { + b.buf = nil + b.offset = 0 +} + +func (b *bytesReader) Read(p []byte) (int, error) { + if b.offset >= len(b.buf) { + return 0, io.EOF + } + + n := len(p) + buf := b.buf[b.offset:] + m := len(buf) + + if n >= m { + copy(p, buf) + b.offset += m + + return m, nil + } + + copy(p, buf[:n]) + b.offset += n + + return n, nil +} + +var _ io.Reader = &bytesReader{} + +func newLexer(data []byte) *jlexer { + l := &jlexer{ + // current: undefToken, + next: undefToken, + } + l.buf = &bytesReader{ + buf: data, + } + l.dec = stdjson.NewDecoder(l.buf) // unfortunately, cannot pool this + + return l +} + +func (l *jlexer) Reset() { + l.err = nil + l.next = undefToken + // leave l.dec and l.buf alone, since they are replaced at every Borrow +} + +func (l *jlexer) Error() error { + return l.err +} + +func (l *jlexer) SetErr(err error) { + l.err = err +} + +func (l *jlexer) Ok() bool { + return l.err == nil +} + +// NextToken consumes a token +func (l *jlexer) NextToken() token { + if !l.Ok() { + return invalidToken + } + + if l.next != undefToken { + next := l.next + l.next = undefToken + + return next + } + + return l.fetchToken() +} + +// PeekToken returns the next token without consuming it +func (l *jlexer) PeekToken() token { + if l.next == undefToken { + l.next = l.fetchToken() + } + + return l.next +} + +func (l *jlexer) Skip() { + _ = l.NextToken() +} + +func (l *jlexer) IsDelim(c byte) bool { + if !l.Ok() { + return false + } + + next := l.PeekToken() + if next.Kind() != tokenDelim { + return false + } + + if next.Delim() != c { + return false + } + + return true +} + +func (l *jlexer) IsNull() bool { + if !l.Ok() { + return false + } + + next := l.PeekToken() + + return next.Kind() == tokenNull +} + +func (l *jlexer) Delim(c byte) { + if !l.Ok() { + return + } + + tok := l.NextToken() + if tok.Kind() != tokenDelim { + l.err = fmt.Errorf("expected a delimiter token but got '%v': %w", tok, ErrStdlib) + + return + } + + if tok.Delim() != c { + l.err = fmt.Errorf("expected delimiter '%q' but got '%q': %w", c, tok.Delim(), ErrStdlib) + } +} + +func (l *jlexer) Null() { + if !l.Ok() { + return + } + + tok := l.NextToken() + if tok.Kind() != tokenNull { + l.err = fmt.Errorf("expected a null token but got '%v': %w", tok, ErrStdlib) + } +} + +func (l *jlexer) Number() any { + if !l.Ok() { + return 0 + } + + tok := l.NextToken() + + switch tok.Kind() { //nolint:exhaustive + case tokenNumber: + n := tok.Token.(stdjson.Number).String() + f, _ := strconv.ParseFloat(n, 64) + if conv.IsFloat64AJSONInteger(f) { + return int64(math.Trunc(f)) + } + + return f + + case tokenFloat: + f := tok.Token.(float64) + if conv.IsFloat64AJSONInteger(f) { + return int64(math.Trunc(f)) + } + + return f + + default: + l.err = fmt.Errorf("expected a number token but got '%v': %w", tok, ErrStdlib) + + return 0 + } +} + +func (l *jlexer) Bool() bool { + if !l.Ok() { + return false + } + + tok := l.NextToken() + if tok.Kind() != tokenBool { + l.err = fmt.Errorf("expected a bool token but got '%v': %w", tok, ErrStdlib) + + return false + } + + return tok.Token.(bool) +} + +func (l *jlexer) String() string { + if !l.Ok() { + return "" + } + + tok := l.NextToken() + if tok.Kind() != tokenString { + l.err = fmt.Errorf("expected a string token but got '%v': %w", tok, ErrStdlib) + + return "" + } + + return tok.Token.(string) +} + +// Commas and colons are elided. +func (l *jlexer) fetchToken() token { + jtok, err := l.dec.Token() + if err != nil { + if errors.Is(err, io.EOF) { + return eofToken + } + + l.err = errors.Join(err, ErrStdlib) + return invalidToken + } + + return token{Token: jtok} +} diff --git a/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/ordered_map.go b/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/ordered_map.go new file mode 100644 index 000000000..54deef406 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/ordered_map.go @@ -0,0 +1,266 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package json + +import ( + stdjson "encoding/json" + "fmt" + "iter" + + "github.com/go-openapi/swag/jsonutils/adapters/ifaces" +) + +var _ ifaces.OrderedMap = &MapSlice{} + +// MapSlice represents a JSON object, with the order of keys maintained. +type MapSlice []MapItem + +func (s MapSlice) OrderedItems() iter.Seq2[string, any] { + return func(yield func(string, any) bool) { + for _, item := range s { + if !yield(item.Key, item.Value) { + return + } + } + } +} + +func (s *MapSlice) SetOrderedItems(items iter.Seq2[string, any]) { + if items == nil { + *s = nil + + return + } + + m := *s + if len(m) > 0 { + // update mode + idx := make(map[string]int, len(m)) + + for i, item := range m { + idx[item.Key] = i + } + + for k, v := range items { + idx, ok := idx[k] + if ok { + m[idx].Value = v + + continue + } + m = append(m, MapItem{Key: k, Value: v}) + } + + *s = m + + return + } + + for k, v := range items { + m = append(m, MapItem{Key: k, Value: v}) + } + + *s = m +} + +// MarshalJSON renders a [MapSlice] as JSON bytes, preserving the order of keys. +func (s MapSlice) MarshalJSON() ([]byte, error) { + return s.OrderedMarshalJSON() +} + +func (s MapSlice) OrderedMarshalJSON() ([]byte, error) { + w := poolOfWriters.Borrow() + defer func() { + poolOfWriters.Redeem(w) + }() + + s.marshalObject(w) + + return w.BuildBytes() // this clones data, so it's okay to redeem the writer and its buffer +} + +// UnmarshalJSON builds a [MapSlice] from JSON bytes, preserving the order of keys. +// +// Inner objects are unmarshaled as [MapSlice] slices and not map[string]any. +func (s *MapSlice) UnmarshalJSON(data []byte) error { + return s.OrderedUnmarshalJSON(data) +} + +func (s *MapSlice) OrderedUnmarshalJSON(data []byte) error { + l := poolOfLexers.Borrow(data) + defer func() { + poolOfLexers.Redeem(l) + }() + + s.unmarshalObject(l) + + return l.Error() +} + +func (s MapSlice) marshalObject(w *jwriter) { + if s == nil { + w.RawString("null") + + return + } + + w.RawByte('{') + + if len(s) == 0 { + w.RawByte('}') + + return + } + + s[0].marshalJSON(w) + + for i := 1; i < len(s); i++ { + w.RawByte(',') + s[i].marshalJSON(w) + } + + w.RawByte('}') +} + +func (s *MapSlice) unmarshalObject(in *jlexer) { + if in.IsNull() { + in.Skip() + + return + } + + in.Delim('{') // consume token + if !in.Ok() { + return + } + + result := make(MapSlice, 0) + + for in.Ok() && !in.IsDelim('}') { + var mi MapItem + + mi.unmarshalKeyValue(in) + result = append(result, mi) + } + + in.Delim('}') + + if !in.Ok() { + return + } + + *s = result +} + +// MapItem represents the value of a key in a JSON object held by [MapSlice]. +// +// Notice that [MapItem] should not be marshaled to or unmarshaled from JSON directly, +// use this type as part of a [MapSlice] when dealing with JSON bytes. +type MapItem struct { + Key string + Value any +} + +func (s MapItem) marshalJSON(w *jwriter) { + w.String(s.Key) + w.RawByte(':') + w.Raw(stdjson.Marshal(s.Value)) +} + +func (s *MapItem) unmarshalKeyValue(in *jlexer) { + key := in.String() // consume string + value := s.asInterface(in) // consume any value, including termination tokens '}' or ']' + + if !in.Ok() { + return + } + + s.Key = key + s.Value = value +} + +func (s *MapItem) unmarshalArray(in *jlexer) []any { + if in.IsNull() { + in.Skip() + + return nil + } + + in.Delim('[') // consume token + if !in.Ok() { + return nil + } + + ret := make([]any, 0) + + for in.Ok() && !in.IsDelim(']') { + ret = append(ret, s.asInterface(in)) + } + + in.Delim(']') + if !in.Ok() { + return nil + } + + return ret +} + +// asInterface is very much like [jlexer.Lexer.Interface], but unmarshals an object +// into a [MapSlice], not a map[string]any. +// +// We have to force parsing errors somehow, since [jlexer.Lexer] doesn't let us +// set a parsing error directly. +func (s *MapItem) asInterface(in *jlexer) any { + if !in.Ok() { + return nil + } + + tok := in.PeekToken() // look-ahead what the next token looks like + kind := tok.Kind() + + switch kind { + case tokenString: + return in.String() // consume string + + case tokenNumber, tokenFloat: + return in.Number() + + case tokenBool: + return in.Bool() + + case tokenNull: + in.Null() + + return nil + + case tokenDelim: + switch tok.Delim() { + case '{': // not consumed yet + ret := make(MapSlice, 0) + ret.unmarshalObject(in) // consumes the terminating '}' + + if in.Ok() { + return ret + } + + // lexer is in an error state: will exhaust + return nil + + case '[': // not consumed yet + return s.unmarshalArray(in) // consumes the terminating ']' + default: + in.SetErr(fmt.Errorf("unexpected delimiter: %v: %w", tok, ErrStdlib)) // force error + return nil + } + + case tokenUndef: + fallthrough + default: + if in.Ok() { + in.SetErr(fmt.Errorf("unexpected token: %v: %w", tok, ErrStdlib)) // force error + } + + return nil + } +} diff --git a/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/pool.go b/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/pool.go new file mode 100644 index 000000000..709b97c30 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/pool.go @@ -0,0 +1,143 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package json + +import ( + "encoding/json" + "sync" + + "github.com/go-openapi/swag/jsonutils/adapters/ifaces" +) + +type adaptersPool struct { + sync.Pool +} + +func (p *adaptersPool) Borrow() *Adapter { + return p.Get().(*Adapter) +} + +func (p *adaptersPool) BorrowIface() ifaces.Adapter { + return p.Get().(*Adapter) +} + +func (p *adaptersPool) Redeem(a *Adapter) { + p.Put(a) +} + +type writersPool struct { + sync.Pool +} + +func (p *writersPool) Borrow() *jwriter { + ptr := p.Get() + + jw := ptr.(*jwriter) + jw.Reset() + + return jw +} + +func (p *writersPool) Redeem(w *jwriter) { + p.Put(w) +} + +type lexersPool struct { + sync.Pool +} + +func (p *lexersPool) Borrow(data []byte) *jlexer { + ptr := p.Get() + + l := ptr.(*jlexer) + l.buf = poolOfReaders.Borrow(data) + l.dec = json.NewDecoder(l.buf) // cannot pool, not exposed by the encoding/json API + l.Reset() + + return l +} + +func (p *lexersPool) Redeem(l *jlexer) { + l.dec = nil + discard := l.buf + l.buf = nil + poolOfReaders.Redeem(discard) + p.Put(l) +} + +type readersPool struct { + sync.Pool +} + +func (p *readersPool) Borrow(data []byte) *bytesReader { + ptr := p.Get() + + b := ptr.(*bytesReader) + b.Reset() + b.buf = data + + return b +} + +func (p *readersPool) Redeem(b *bytesReader) { + p.Put(b) +} + +var ( + poolOfAdapters = &adaptersPool{ + Pool: sync.Pool{ + New: func() any { + return NewAdapter() + }, + }, + } + + poolOfWriters = &writersPool{ + Pool: sync.Pool{ + New: func() any { + return newJWriter() + }, + }, + } + + poolOfLexers = &lexersPool{ + Pool: sync.Pool{ + New: func() any { + return newLexer(nil) + }, + }, + } + + poolOfReaders = &readersPool{ + Pool: sync.Pool{ + New: func() any { + return &bytesReader{} + }, + }, + } +) + +// BorrowAdapter borrows an [Adapter] from the pool, recycling already allocated instances. +func BorrowAdapter() *Adapter { + return poolOfAdapters.Borrow() +} + +// BorrowAdapterIface borrows a stdlib [Adapter] and converts it directly +// to [ifaces.Adapter]. This is useful to avoid further allocations when +// translating the concrete type into an interface. +func BorrowAdapterIface() ifaces.Adapter { + return poolOfAdapters.BorrowIface() +} + +// RedeemAdapter redeems an [Adapter] to the pool, so it may be recycled. +func RedeemAdapter(a *Adapter) { + poolOfAdapters.Redeem(a) +} + +func RedeemAdapterIface(a ifaces.Adapter) { + concrete, ok := a.(*Adapter) + if ok { + poolOfAdapters.Redeem(concrete) + } +} diff --git a/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/register.go b/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/register.go new file mode 100644 index 000000000..fc8818694 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/register.go @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package json + +import ( + "fmt" + "reflect" + + "github.com/go-openapi/swag/jsonutils/adapters/ifaces" +) + +func Register(dispatcher ifaces.Registrar) { + t := reflect.TypeOf(Adapter{}) + dispatcher.RegisterFor( + ifaces.RegistryEntry{ + Who: fmt.Sprintf("%s.%s", t.PkgPath(), t.Name()), + What: ifaces.AllCapabilities, + Constructor: BorrowAdapterIface, + Support: support, + }) +} + +func support(_ ifaces.Capability, _ any) bool { + return true +} diff --git a/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/writer.go b/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/writer.go new file mode 100644 index 000000000..dc2325c1a --- /dev/null +++ b/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/writer.go @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package json + +import ( + "bytes" + "encoding/json" + "strings" +) + +type jwriter struct { + buf *bytes.Buffer + err error +} + +func newJWriter() *jwriter { + buf := make([]byte, 0, sensibleBufferSize) + + return &jwriter{buf: bytes.NewBuffer(buf)} +} + +func (w *jwriter) Reset() { + w.buf.Reset() + w.err = nil +} + +func (w *jwriter) RawString(s string) { + if w.err != nil { + return + } + w.buf.WriteString(s) +} + +func (w *jwriter) Raw(b []byte, err error) { + if w.err != nil { + return + } + if err != nil { + w.err = err + return + } + + _, _ = w.buf.Write(b) +} + +func (w *jwriter) RawByte(c byte) { + if w.err != nil { + return + } + w.buf.WriteByte(c) +} + +var quoteReplacer = strings.NewReplacer(`"`, `\"`, `\`, `\\`) + +func (w *jwriter) String(s string) { + if w.err != nil { + return + } + // escape quotes and \ + s = quoteReplacer.Replace(s) + + _ = w.buf.WriteByte('"') + json.HTMLEscape(w.buf, []byte(s)) + _ = w.buf.WriteByte('"') +} + +// BuildBytes returns a clone of the internal buffer. +func (w *jwriter) BuildBytes() ([]byte, error) { + if w.err != nil { + return nil, w.err + } + + return bytes.Clone(w.buf.Bytes()), nil +} diff --git a/vendor/github.com/go-openapi/swag/jsonutils/concat.go b/vendor/github.com/go-openapi/swag/jsonutils/concat.go index 4c96ec75d..2068503af 100644 --- a/vendor/github.com/go-openapi/swag/jsonutils/concat.go +++ b/vendor/github.com/go-openapi/swag/jsonutils/concat.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package jsonutils @@ -32,7 +21,10 @@ func init() { } } -// ConcatJSON concatenates multiple json objects efficiently +// ConcatJSON concatenates multiple json objects or arrays efficiently. +// +// Note that [ConcatJSON] performs a very simple (and fast) concatenation +// operation: it does not attempt to merge objects. func ConcatJSON(blobs ...[]byte) []byte { if len(blobs) == 0 { return nil diff --git a/vendor/github.com/go-openapi/swag/jsonutils/doc.go b/vendor/github.com/go-openapi/swag/jsonutils/doc.go index 495ef8341..3926cc58d 100644 --- a/vendor/github.com/go-openapi/swag/jsonutils/doc.go +++ b/vendor/github.com/go-openapi/swag/jsonutils/doc.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 // Package jsonutils provides helpers to work with JSON. // diff --git a/vendor/github.com/go-openapi/swag/jsonutils/json.go b/vendor/github.com/go-openapi/swag/jsonutils/json.go index 62a3684ed..40753ce03 100644 --- a/vendor/github.com/go-openapi/swag/jsonutils/json.go +++ b/vendor/github.com/go-openapi/swag/jsonutils/json.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package jsonutils @@ -18,30 +7,44 @@ import ( "bytes" "encoding/json" - "github.com/mailru/easyjson" - "github.com/mailru/easyjson/jlexer" - "github.com/mailru/easyjson/jwriter" + "github.com/go-openapi/swag/jsonutils/adapters" + "github.com/go-openapi/swag/jsonutils/adapters/ifaces" ) -type ejMarshaler = easyjson.Marshaler -type ejUnmarshaler = easyjson.Unmarshaler - // WriteJSON marshals a data structure as JSON. // // The difference with [json.Marshal] is that it may check among several alternatives // to do so. // -// Currently this allows types that are [easyjson.Marshaler]s to use that route to produce JSON. -func WriteJSON(value interface{}) ([]byte, error) { - if d, ok := value.(ejMarshaler); ok { - jw := new(jwriter.Writer) - d.MarshalEasyJSON(jw) - return jw.BuildBytes() +// See [adapters.Registrar] for more details about how to configure +// multiple serialization alternatives. +// +// NOTE: to allow types that are [easyjson.Marshaler] s to use that route to process JSON, +// you now need to register the adapter for easyjson at runtime. +func WriteJSON(value any) ([]byte, error) { + if orderedMap, isOrdered := value.(ifaces.Ordered); isOrdered { + orderedMarshaler := adapters.OrderedMarshalAdapterFor(orderedMap) + + if orderedMarshaler != nil { + defer orderedMarshaler.Redeem() + + return orderedMarshaler.OrderedMarshal(orderedMap) + } + + // no support found in registered adapters, fallback to the default (unordered) case } - if d, ok := value.(json.Marshaler); ok { - return d.MarshalJSON() + + marshaler := adapters.MarshalAdapterFor(value) + if marshaler != nil { + defer marshaler.Redeem() + + return marshaler.Marshal(value) } - return json.Marshal(value) + + // no support found in registered adapters, fallback to the default standard library. + // + // This only happens when tinkering with the global registry of adapters, since the default handles all the above cases. + return json.Marshal(value) // Codecov ignore // this is a safeguard not easily simulated in tests } // ReadJSON unmarshals JSON data into a data structure. @@ -49,28 +52,61 @@ func WriteJSON(value interface{}) ([]byte, error) { // The difference with [json.Unmarshal] is that it may check among several alternatives // to do so. // -// Currently this allows types that are [easyjson.Unmarshaler]s to use that route to process JSON. -func ReadJSON(data []byte, value interface{}) error { +// See [adapters.Registrar] for more details about how to configure +// multiple serialization alternatives. +// +// NOTE: value must be a pointer. +// +// If the provided value implements [ifaces.SetOrdered], it is a considered an "ordered map" and [ReadJSON] +// will favor an adapter that supports the [ifaces.OrderedUnmarshal] feature, or fallback to +// an unordered behavior if none is found. +// +// NOTE: to allow types that are [easyjson.Unmarshaler] s to use that route to process JSON, +// you now need to register the adapter for easyjson at runtime. +func ReadJSON(data []byte, value any) error { trimmedData := bytes.Trim(data, "\x00") - if d, ok := value.(ejUnmarshaler); ok { - jl := &jlexer.Lexer{Data: trimmedData} - d.UnmarshalEasyJSON(jl) - return jl.Error() + + if orderedMap, isOrdered := value.(ifaces.SetOrdered); isOrdered { + // if the value is an ordered map, favors support for OrderedUnmarshal. + + orderedUnmarshaler := adapters.OrderedUnmarshalAdapterFor(orderedMap) + + if orderedUnmarshaler != nil { + defer orderedUnmarshaler.Redeem() + + return orderedUnmarshaler.OrderedUnmarshal(trimmedData, orderedMap) + } + + // no support found in registered adapters, fallback to the default (unordered) case } - if d, ok := value.(json.Unmarshaler); ok { - return d.UnmarshalJSON(trimmedData) + unmarshaler := adapters.UnmarshalAdapterFor(value) + if unmarshaler != nil { + defer unmarshaler.Redeem() + + return unmarshaler.Unmarshal(trimmedData, value) } - return json.Unmarshal(trimmedData, value) + // no support found in registered adapters, fallback to the default standard library. + // + // This only happens when tinkering with the global registry of adapters, since the default handles all the above cases. + return json.Unmarshal(trimmedData, value) // Codecov ignore // this is a safeguard not easily simulated in tests } // FromDynamicJSON turns a go value into a properly JSON typed structure. // -// "Dynamic JSON" refers to what you get when unmarshaling JSON into an untyped interface{}, -// i.e. objects are represented by map[string]interface{}, arrays by []interface{}, and +// "Dynamic JSON" refers to what you get when unmarshaling JSON into an untyped any, +// i.e. objects are represented by map[string]any, arrays by []any, and // all numbers are represented as float64. -func FromDynamicJSON(source, target interface{}) error { +// +// NOTE: target must be a pointer. +// +// # Maintaining the order of keys in objects +// +// If source and target implement [ifaces.Ordered] and [ifaces.SetOrdered] respectively, +// they are considered "ordered maps" and the order of keys is maintained in the +// "jsonification" process. In that case, map[string]any values are replaced by (ordered) [JSONMapSlice] ones. +func FromDynamicJSON(source, target any) error { b, err := WriteJSON(source) if err != nil { return err diff --git a/vendor/github.com/go-openapi/swag/jsonutils/ordered_map.go b/vendor/github.com/go-openapi/swag/jsonutils/ordered_map.go index e11fd4bfb..38dd3e244 100644 --- a/vendor/github.com/go-openapi/swag/jsonutils/ordered_map.go +++ b/vendor/github.com/go-openapi/swag/jsonutils/ordered_map.go @@ -1,199 +1,114 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package jsonutils import ( - "strconv" - "strings" + "iter" - "github.com/mailru/easyjson/jlexer" - "github.com/mailru/easyjson/jwriter" + "github.com/go-openapi/swag/jsonutils/adapters" + "github.com/go-openapi/swag/typeutils" ) // JSONMapSlice represents a JSON object, with the order of keys maintained. +// +// It behaves like an ordered map, but keys can't be accessed in constant time. type JSONMapSlice []JSONMapItem -// MarshalJSON renders a [JSONMapSlice] as JSON bytes, preserving the order of keys. -func (s JSONMapSlice) MarshalJSON() ([]byte, error) { - w := &jwriter.Writer{Flags: jwriter.NilMapAsEmpty | jwriter.NilSliceAsEmpty} - s.MarshalEasyJSON(w) - - return w.BuildBytes() +// OrderedItems iterates over all (key,value) pairs with the order of keys maintained. +// +// This implements the [ifaces.Ordered] interface, so that [ifaces.Adapter] s know how to marshal +// keys in the desired order. +func (s JSONMapSlice) OrderedItems() iter.Seq2[string, any] { + return func(yield func(string, any) bool) { + for _, item := range s { + if !yield(item.Key, item.Value) { + return + } + } + } } -// MarshalEasyJSON renders a [JSONMapSlice] as JSON bytes, using easyJSON -func (s JSONMapSlice) MarshalEasyJSON(w *jwriter.Writer) { - if s == nil { - w.RawString("null") +// SetOrderedItems sets keys in the [JSONMapSlice] objects, as presented by +// the provided iterator. +// +// As a special case, if items is nil, this sets to receiver to a nil slice. +// +// This implements the [ifaces.SetOrdered] interface, so that [ifaces.Adapter] s know how to unmarshal +// keys in the desired order. +func (s *JSONMapSlice) SetOrderedItems(items iter.Seq2[string, any]) { + if items == nil { + // force receiver to be a nil slice + *s = nil return } - w.RawByte('{') + m := *s + if len(m) > 0 { + // update mode: short-circuited when unmarshaling fresh data structures + idx := make(map[string]int, len(m)) + + for i, item := range m { + idx[item.Key] = i + } + + for k, v := range items { + idx, ok := idx[k] + if ok { + m[idx].Value = v + + continue + } + + m = append(m, JSONMapItem{Key: k, Value: v}) + } - if len(s) == 0 { - w.RawByte('}') + *s = m return } - s[0].MarshalEasyJSON(w) - - for i := 1; i < len(s); i++ { - w.RawByte(',') - s[i].MarshalEasyJSON(w) + for k, v := range items { + m = append(m, JSONMapItem{Key: k, Value: v}) } - w.RawByte('}') + *s = m } -// UnmarshalJSON builds a [JSONMapSlice] from JSON bytes, preserving the order of keys. +// MarshalJSON renders a [JSONMapSlice] as JSON bytes, preserving the order of keys. // -// Inner objects are unmarshaled as [JSONMapSlice] slices and not map[string]any. -func (s *JSONMapSlice) UnmarshalJSON(data []byte) error { - l := jlexer.Lexer{Data: data} - s.UnmarshalEasyJSON(&l) +// It will pick the JSON library currently configured by the [adapters.Registry] (defaults to the standard library). +func (s JSONMapSlice) MarshalJSON() ([]byte, error) { + orderedMarshaler := adapters.OrderedMarshalAdapterFor(s) + defer orderedMarshaler.Redeem() - return l.Error() + return orderedMarshaler.OrderedMarshal(s) } -// UnmarshalEasyJSON builds a [JSONMapSlice] from JSON bytes, using easyJSON -func (s *JSONMapSlice) UnmarshalEasyJSON(in *jlexer.Lexer) { - if in.IsNull() { - in.Skip() - - return +// UnmarshalJSON builds a [JSONMapSlice] from JSON bytes, preserving the order of keys. +// +// Inner objects are unmarshaled as ordered [JSONMapSlice] slices and not map[string]any. +// +// It will pick the JSON library currently configured by the [adapters.Registry] (defaults to the standard library). +func (s *JSONMapSlice) UnmarshalJSON(data []byte) error { + if typeutils.IsNil(*s) { + // allow to unmarshal with a simple var declaration (nil slice) + *s = JSONMapSlice{} } - result := make(JSONMapSlice, 0) - in.Delim('{') - for !in.IsDelim('}') { - var mi JSONMapItem - mi.UnmarshalEasyJSON(in) - result = append(result, mi) - } - in.Delim('}') + orderedUnmarshaler := adapters.OrderedUnmarshalAdapterFor(s) + defer orderedUnmarshaler.Redeem() - *s = result + return orderedUnmarshaler.OrderedUnmarshal(data, s) } // JSONMapItem represents the value of a key in a JSON object held by [JSONMapSlice]. // -// Notice that JSONMapItem should not be marshaled to or unmarshaled from JSON directly, -// use this type as part of a [JSONMapSlice] when dealing with JSON bytes. +// Notice that JSONMapItem should not be marshaled to or unmarshaled from JSON directly. +// +// Use this type as part of a [JSONMapSlice] when dealing with JSON bytes. type JSONMapItem struct { Key string Value any } - -// MarshalEasyJSON renders a [JSONMapItem] as JSON bytes, using easyJSON -func (s JSONMapItem) MarshalEasyJSON(w *jwriter.Writer) { - w.String(s.Key) - w.RawByte(':') - w.Raw(WriteJSON(s.Value)) -} - -// UnmarshalEasyJSON builds a [JSONMapItem] from JSON bytes, using easyJSON -func (s *JSONMapItem) UnmarshalEasyJSON(in *jlexer.Lexer) { - key := in.UnsafeString() - in.WantColon() - value := s.asInterface(in) - in.WantComma() - - s.Key = key - s.Value = value -} - -// asInterface is very much like [jlexer.Lexer.Interface], but unmarshals an object -// into a [JSONMapSlice], not a map[string]any. -// -// We have to force parsing errors somehow, since [jlexer.Lexer] doesn't let us -// set a parsing error directly. -func (s *JSONMapItem) asInterface(in *jlexer.Lexer) any { - tokenKind := in.CurrentToken() - - if !in.Ok() { - return nil - } - - switch tokenKind { - case jlexer.TokenString: - return in.String() - - case jlexer.TokenNumber: - // determine if we may use an integer type - n := in.JsonNumber().String() - if strings.ContainsRune(n, '.') { - f, _ := strconv.ParseFloat(n, 64) - return f - } - - i, _ := strconv.ParseInt(n, 10, 64) - return i - - case jlexer.TokenBool: - return in.Bool() - - case jlexer.TokenNull: - in.Null() - return nil - - case jlexer.TokenDelim: - if in.IsDelim('{') { - ret := make(JSONMapSlice, 0) - ret.UnmarshalEasyJSON(in) - - if in.Ok() { - return ret - } - - // lexer is in an error state: will exhaust - return nil - } - - if in.IsDelim('[') { - in.Delim('[') // consume - - ret := []interface{}{} - for !in.IsDelim(']') { - ret = append(ret, s.asInterface(in)) - in.WantComma() - } - in.Delim(']') - - if in.Ok() { - return ret - } - - // lexer is in an error state: will exhaust - return nil - } - - if in.Ok() { - in.Delim('{') // force error - } - - return nil - - case jlexer.TokenUndef: - fallthrough - default: - if in.Ok() { - in.Delim('{') // force error - } - - return nil - } -} diff --git a/vendor/github.com/go-openapi/swag/jsonutils_iface.go b/vendor/github.com/go-openapi/swag/jsonutils_iface.go index 63e23f0b6..7bd4105fa 100644 --- a/vendor/github.com/go-openapi/swag/jsonutils_iface.go +++ b/vendor/github.com/go-openapi/swag/jsonutils_iface.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package swag @@ -33,17 +22,17 @@ type JSONMapItem = jsonutils.JSONMapItem // WriteJSON writes json data. // // Deprecated: use [jsonutils.WriteJSON] instead. -func WriteJSON(data interface{}) ([]byte, error) { return jsonutils.WriteJSON(data) } +func WriteJSON(data any) ([]byte, error) { return jsonutils.WriteJSON(data) } // ReadJSON reads json data. // // Deprecated: use [jsonutils.ReadJSON] instead. -func ReadJSON(data []byte, value interface{}) error { return jsonutils.ReadJSON(data, value) } +func ReadJSON(data []byte, value any) error { return jsonutils.ReadJSON(data, value) } // DynamicJSONToStruct converts an untyped JSON structure into a target data type. // // Deprecated: use [jsonutils.FromDynamicJSON] instead. -func DynamicJSONToStruct(data interface{}, target interface{}) error { +func DynamicJSONToStruct(data any, target any) error { return jsonutils.FromDynamicJSON(data, target) } @@ -57,8 +46,8 @@ func ConcatJSON(blobs ...[]byte) []byte { return jsonutils.ConcatJSON(blobs...) // It is the same as [FromDynamicJSON], but doesn't check for errors. // // Deprecated: this function is a misnomer and is unsafe. Use [jsonutils.FromDynamicJSON] instead. -func ToDynamicJSON(value interface{}) interface{} { - var res interface{} +func ToDynamicJSON(value any) any { + var res any if err := FromDynamicJSON(value, &res); err != nil { log.Println(err) } @@ -68,9 +57,9 @@ func ToDynamicJSON(value interface{}) interface{} { // FromDynamicJSON turns a go value into a properly JSON typed structure. // -// "Dynamic JSON" refers to what you get when unmarshaling JSON into an untyped interface{}, -// i.e. objects are represented by map[string]interface{}, arrays by []interface{}, and all -// scalar values are interface{}. +// "Dynamic JSON" refers to what you get when unmarshaling JSON into an untyped any, +// i.e. objects are represented by map[string]any, arrays by []any, and all +// scalar values are any. // // Deprecated: use [jsonutils.FromDynamicJSON] instead. -func FromDynamicJSON(data, target interface{}) error { return jsonutils.FromDynamicJSON(data, target) } +func FromDynamicJSON(data, target any) error { return jsonutils.FromDynamicJSON(data, target) } diff --git a/vendor/github.com/go-openapi/swag/loading/doc.go b/vendor/github.com/go-openapi/swag/loading/doc.go index 62585615e..8cf7bcb8b 100644 --- a/vendor/github.com/go-openapi/swag/loading/doc.go +++ b/vendor/github.com/go-openapi/swag/loading/doc.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 // Package loading provides tools to load a file from http or from a local file system. package loading diff --git a/vendor/github.com/go-openapi/swag/loading/errors.go b/vendor/github.com/go-openapi/swag/loading/errors.go index ca45732a7..b3964289c 100644 --- a/vendor/github.com/go-openapi/swag/loading/errors.go +++ b/vendor/github.com/go-openapi/swag/loading/errors.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package loading diff --git a/vendor/github.com/go-openapi/swag/loading/json.go b/vendor/github.com/go-openapi/swag/loading/json.go index aadf99913..59db12f5c 100644 --- a/vendor/github.com/go-openapi/swag/loading/json.go +++ b/vendor/github.com/go-openapi/swag/loading/json.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + package loading import ( diff --git a/vendor/github.com/go-openapi/swag/loading/loading.go b/vendor/github.com/go-openapi/swag/loading/loading.go index bd955535f..269fb74d1 100644 --- a/vendor/github.com/go-openapi/swag/loading/loading.go +++ b/vendor/github.com/go-openapi/swag/loading/loading.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package loading diff --git a/vendor/github.com/go-openapi/swag/loading/options.go b/vendor/github.com/go-openapi/swag/loading/options.go index a51329e93..6674ac69e 100644 --- a/vendor/github.com/go-openapi/swag/loading/options.go +++ b/vendor/github.com/go-openapi/swag/loading/options.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package loading @@ -81,7 +70,7 @@ func WithCustomHeaders(headers map[string]string) Option { } } -// WithHTTClient overrides the default HTTP client used to fetch a remote file. +// WithHTTPClient overrides the default HTTP client used to fetch a remote file. // // By default, [http.DefaultClient] is used. func WithHTTPClient(client *http.Client) Option { @@ -90,7 +79,7 @@ func WithHTTPClient(client *http.Client) Option { } } -// WithFileFS sets a file system for the local file loader. +// WithFS sets a file system for the local file loader. // // If the provided file system is a [fs.ReadFileFS], the ReadFile function is used. // Otherwise, ReadFile is wrapped using [fs.ReadFile]. diff --git a/vendor/github.com/go-openapi/swag/loading/yaml.go b/vendor/github.com/go-openapi/swag/loading/yaml.go index 40bd2a769..3ebb53668 100644 --- a/vendor/github.com/go-openapi/swag/loading/yaml.go +++ b/vendor/github.com/go-openapi/swag/loading/yaml.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package loading @@ -38,7 +27,7 @@ func YAMLDoc(path string, opts ...Option) (json.RawMessage, error) { } // YAMLData loads a yaml document from either http or a file. -func YAMLData(path string, opts ...Option) (interface{}, error) { +func YAMLData(path string, opts ...Option) (any, error) { data, err := LoadFromFileOrHTTP(path, opts...) if err != nil { return nil, err diff --git a/vendor/github.com/go-openapi/swag/loading_iface.go b/vendor/github.com/go-openapi/swag/loading_iface.go index 38d825bc5..27ec3fb8c 100644 --- a/vendor/github.com/go-openapi/swag/loading_iface.go +++ b/vendor/github.com/go-openapi/swag/loading_iface.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package swag @@ -84,7 +73,7 @@ func YAMLDoc(path string) (json.RawMessage, error) { // YAMLData loads a yaml document from either http or a file. // // Deprecated: use [loading.YAMLData] instead. -func YAMLData(path string) (interface{}, error) { +func YAMLData(path string) (any, error) { return loading.YAMLData(path) } diff --git a/vendor/github.com/go-openapi/swag/mangling/doc.go b/vendor/github.com/go-openapi/swag/mangling/doc.go index dbe806828..ce0d89048 100644 --- a/vendor/github.com/go-openapi/swag/mangling/doc.go +++ b/vendor/github.com/go-openapi/swag/mangling/doc.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 // Package mangling provides name mangling capabilities. // diff --git a/vendor/github.com/go-openapi/swag/mangling/initialism_index.go b/vendor/github.com/go-openapi/swag/mangling/initialism_index.go index cf0786f81..e5b70c149 100644 --- a/vendor/github.com/go-openapi/swag/mangling/initialism_index.go +++ b/vendor/github.com/go-openapi/swag/mangling/initialism_index.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package mangling @@ -190,6 +179,19 @@ const ( simplePlural ) +func (f pluralForm) String() string { + switch f { + case notPlural: + return "notPlural" + case invariantPlural: + return "invariantPlural" + case simplePlural: + return "simplePlural" + default: + return "" + } +} + // pluralForm indicates how we want to pluralize a given initialism. // // Besides configured invariant forms (like HTTP and HTTPS), diff --git a/vendor/github.com/go-openapi/swag/mangling/name_lexem.go b/vendor/github.com/go-openapi/swag/mangling/name_lexem.go index 02004b4f3..bc837e3b9 100644 --- a/vendor/github.com/go-openapi/swag/mangling/name_lexem.go +++ b/vendor/github.com/go-openapi/swag/mangling/name_lexem.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package mangling diff --git a/vendor/github.com/go-openapi/swag/mangling/name_mangler.go b/vendor/github.com/go-openapi/swag/mangling/name_mangler.go index 94ae555a7..da685681d 100644 --- a/vendor/github.com/go-openapi/swag/mangling/name_mangler.go +++ b/vendor/github.com/go-openapi/swag/mangling/name_mangler.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package mangling diff --git a/vendor/github.com/go-openapi/swag/mangling/options.go b/vendor/github.com/go-openapi/swag/mangling/options.go index 66ad2e46c..3c92b2f18 100644 --- a/vendor/github.com/go-openapi/swag/mangling/options.go +++ b/vendor/github.com/go-openapi/swag/mangling/options.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package mangling diff --git a/vendor/github.com/go-openapi/swag/mangling/pools.go b/vendor/github.com/go-openapi/swag/mangling/pools.go index d85b40387..f81043514 100644 --- a/vendor/github.com/go-openapi/swag/mangling/pools.go +++ b/vendor/github.com/go-openapi/swag/mangling/pools.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package mangling diff --git a/vendor/github.com/go-openapi/swag/mangling/split.go b/vendor/github.com/go-openapi/swag/mangling/split.go index 40e4a2e0e..ed12ea256 100644 --- a/vendor/github.com/go-openapi/swag/mangling/split.go +++ b/vendor/github.com/go-openapi/swag/mangling/split.go @@ -1,20 +1,10 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package mangling import ( + "fmt" "unicode" ) @@ -47,6 +37,13 @@ type ( initialismMatches []initialismMatch ) +// String representation of a match, e.g. for debugging. +func (m initialismMatch) String() string { + return fmt.Sprintf("{body: %s (%d), start: %d, end; %d, complete: %t, hasPlural: %v}", + string(m.body), len(m.body), m.start, m.end, m.complete, m.hasPlural, + ) +} + func (m initialismMatch) isZero() bool { return m.start == 0 && m.end == 0 } @@ -83,116 +80,148 @@ func (s splitter) split(name string) *[]nameLexem { } func (s splitter) gatherInitialismMatches(nameRunes []rune) *initialismMatches { - var matches *initialismMatches + matches := poolOfMatches.BorrowMatches() + const minLenInitialism = 1 + if len(nameRunes) < minLenInitialism+1 { + // can't match initialism with 0 or 1 rune + return matches + } + + // first iteration + s.findMatches(matches, nameRunes, nameRunes[0], 0) - for currentRunePosition, currentRune := range nameRunes { - // recycle these allocations as we loop over runes + for i, currentRune := range nameRunes[1:] { + currentRunePosition := i + 1 + // recycle allocations as we loop over runes // with such recycling, only 2 slices should be allocated per call // instead of o(n). + // + // BorrowMatches always yields slices with zero length (with some capacity) newMatches := poolOfMatches.BorrowMatches() // check current initialism matches - if matches != nil { // skip first iteration - for _, match := range *matches { - if keepCompleteMatch := match.complete; keepCompleteMatch { - *newMatches = append(*newMatches, match) - - // the match is complete: keep it then move on to next rune - continue - } + for _, match := range *matches { + if keepCompleteMatch := match.complete; keepCompleteMatch { + // the match is already complete: keep it then move on to the next match + *newMatches = append(*newMatches, match) + continue + } - currentMatchRune := match.body[currentRunePosition-match.start] - if currentMatchRune != currentRune { - // failed match, move on to next rune - continue - } + if currentRunePosition-match.start == len(match.body) { + // unmatched: skip + continue + } - // try to complete ongoing match - if currentRunePosition-match.start == len(match.body)-1 { - // we are close; the next step is to check the symbol ahead - // if it is a lowercase letter, then it is not the end of match - // but the beginning of the next word. - // - // NOTE(fredbi): this heuristic sometimes leads to counterintuitive splits and - // perhaps (not sure yet) we should check against case _alternance_. - // - // Example: - // - // In the current version, in the sentence "IDS initialism", "ID" is recognized as an initialism, - // leading to a split like "id_s_initialism" (or IDSInitialism), - // whereas in the sentence "IDx initialism", it is not and produces something like - // "i_d_x_initialism" (or IDxInitialism). The generated file name is not great. - // - // Both go identifiers are tolerated by linters. - // - // Notice that the slightly different input "IDs initialism" is correctly detected - // as a pluralized initialism and produces something like "ids_initialism" (or IDsInitialism). - - if currentRunePosition < len(nameRunes)-1 { - nextRune := nameRunes[currentRunePosition+1] - - // recognize a plural form for this initialism (only simple pluralization is supported) - if nextRune == 's' && match.hasPlural == simplePlural { - // detected a pluralized initialism - match.body = append(match.body, nextRune) - currentRunePosition++ - if currentRunePosition < len(nameRunes)-1 { - nextRune = nameRunes[currentRunePosition+1] - if newWord := unicode.IsLower(nextRune); newWord { - // it is the start of a new word. - // Match is only partial and the initialism is not recognized : move on - continue - } - } + // 1. by construction of the matches, we can't have currentRunePosition - match.start < 0 + // because matches have been computed with their start <= currentRunePosition in the previous + // iterations. + // 2. by construction of the matches, we can't have currentRunePosition - match.start >= len(match.body) - // this is a pluralized match: keep it - match.complete = true - match.hasPlural = simplePlural - match.end = currentRunePosition - *newMatches = append(*newMatches, match) + currentMatchRune := match.body[currentRunePosition-match.start] + if currentMatchRune != currentRune { + // failed match, discard it then move on to the next match + continue + } - // match is complete: keep it then move on to next rune - continue + // try to complete the current match + if currentRunePosition-match.start == len(match.body)-1 { + // we are close: the next step is to check the symbol ahead + // if it is a lowercase letter, then it is not the end of match + // but the beginning of the next word. + // + // NOTE(fredbi): this heuristic sometimes leads to counterintuitive splits and + // perhaps (not sure yet) we should check against case _alternance_. + // + // Example: + // + // In the current version, in the sentence "IDS initialism", "ID" is recognized as an initialism, + // leading to a split like "id_s_initialism" (or IDSInitialism), + // whereas in the sentence "IDx initialism", it is not and produces something like + // "i_d_x_initialism" (or IDxInitialism). The generated file name is not great. + // + // Both go identifiers are tolerated by linters. + // + // Notice that the slightly different input "IDs initialism" is correctly detected + // as a pluralized initialism and produces something like "ids_initialism" (or IDsInitialism). + + if currentRunePosition < len(nameRunes)-1 { // when before the last rune + nextRune := nameRunes[currentRunePosition+1] + + // recognize a plural form for this initialism (only simple english pluralization is supported). + if nextRune == 's' && match.hasPlural == simplePlural { + // detected a pluralized initialism + match.body = append(match.body, nextRune) + lookAhead := currentRunePosition + 1 + if lookAhead < len(nameRunes)-1 { + nextRune = nameRunes[lookAhead+1] + if newWord := unicode.IsLower(nextRune); newWord { + // it is the start of a new word. + // Match is only partial and the initialism is not recognized: + // move on to the next match, but do not advance the rune position + continue + } } - if newWord := unicode.IsLower(nextRune); newWord { - // it is the start of a new word - // Match is only partial and the initialism is not recognized : move on - continue - } + // this is a pluralized match: keep it + currentRunePosition++ + match.complete = true + match.hasPlural = simplePlural + match.end = currentRunePosition + *newMatches = append(*newMatches, match) + + // match is complete: keep it then move on to the next match + continue } - match.complete = true - match.end = currentRunePosition + // other cases + // example: invariant plural such as "TLS" + if newWord := unicode.IsLower(nextRune); newWord { + // it is the start of a new word + // Match is only partial and the initialism is not recognized : move on + continue + } } - // append the ongoing matching attempt (not necessarily complete) - *newMatches = append(*newMatches, match) + match.complete = true + match.end = currentRunePosition } - } - // check for new initialism matches - for i, r := range s.initialismsRunes { - if r[0] == currentRune { - *newMatches = append(*newMatches, initialismMatch{ - start: currentRunePosition, - body: r, - complete: false, - hasPlural: s.initialismsPluralForm[i], - }) - } + // append the ongoing matching attempt: it is not necessarily complete, but was successful so far. + // Let's see if it still matches on the next rune. + *newMatches = append(*newMatches, match) } - if matches != nil { - poolOfMatches.RedeemMatches(matches) - } + s.findMatches(newMatches, nameRunes, currentRune, currentRunePosition) + + poolOfMatches.RedeemMatches(matches) matches = newMatches } - // up to the caller to redeem this last slice + // it is up to the caller to redeem this last slice return matches } +func (s splitter) findMatches(newMatches *initialismMatches, nameRunes []rune, currentRune rune, currentRunePosition int) { + // check for new initialism matches, based on the first character + for i, r := range s.initialismsRunes { + if r[0] != currentRune { + continue + } + + if currentRunePosition+len(r) > len(nameRunes) { + continue // not eligible: would spilll over the initial string + } + + // possible matches: all initialisms starting with the current rune and that can fit the given string (nameRunes) + *newMatches = append(*newMatches, initialismMatch{ + start: currentRunePosition, + body: r, + complete: false, + hasPlural: s.initialismsPluralForm[i], + }) + } +} + func (s splitter) mapMatchesToNameLexems(nameRunes []rune, matches *initialismMatches) *[]nameLexem { nameLexems := poolOfLexems.BorrowLexems() diff --git a/vendor/github.com/go-openapi/swag/mangling/string_bytes.go b/vendor/github.com/go-openapi/swag/mangling/string_bytes.go index 06351434d..28daaf72b 100644 --- a/vendor/github.com/go-openapi/swag/mangling/string_bytes.go +++ b/vendor/github.com/go-openapi/swag/mangling/string_bytes.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package mangling diff --git a/vendor/github.com/go-openapi/swag/mangling/util.go b/vendor/github.com/go-openapi/swag/mangling/util.go index c289dc6bd..0636417e3 100644 --- a/vendor/github.com/go-openapi/swag/mangling/util.go +++ b/vendor/github.com/go-openapi/swag/mangling/util.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package mangling diff --git a/vendor/github.com/go-openapi/swag/mangling_iface.go b/vendor/github.com/go-openapi/swag/mangling_iface.go index 2d0d07ddb..98b9a9992 100644 --- a/vendor/github.com/go-openapi/swag/mangling_iface.go +++ b/vendor/github.com/go-openapi/swag/mangling_iface.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package swag diff --git a/vendor/github.com/go-openapi/swag/netutils/doc.go b/vendor/github.com/go-openapi/swag/netutils/doc.go index ed6d8a022..74282f8e5 100644 --- a/vendor/github.com/go-openapi/swag/netutils/doc.go +++ b/vendor/github.com/go-openapi/swag/netutils/doc.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 // Package netutils provides helpers for network-related tasks. package netutils diff --git a/vendor/github.com/go-openapi/swag/netutils/net.go b/vendor/github.com/go-openapi/swag/netutils/net.go index 3d0182fc5..82a1544af 100644 --- a/vendor/github.com/go-openapi/swag/netutils/net.go +++ b/vendor/github.com/go-openapi/swag/netutils/net.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package netutils diff --git a/vendor/github.com/go-openapi/swag/netutils_iface.go b/vendor/github.com/go-openapi/swag/netutils_iface.go index 537314e36..d658de25b 100644 --- a/vendor/github.com/go-openapi/swag/netutils_iface.go +++ b/vendor/github.com/go-openapi/swag/netutils_iface.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package swag diff --git a/vendor/github.com/go-openapi/swag/stringutils/collection_formats.go b/vendor/github.com/go-openapi/swag/stringutils/collection_formats.go index 1ff96dcbd..28056ad25 100644 --- a/vendor/github.com/go-openapi/swag/stringutils/collection_formats.go +++ b/vendor/github.com/go-openapi/swag/stringutils/collection_formats.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package stringutils diff --git a/vendor/github.com/go-openapi/swag/stringutils/doc.go b/vendor/github.com/go-openapi/swag/stringutils/doc.go index b5d18e517..c6d17a116 100644 --- a/vendor/github.com/go-openapi/swag/stringutils/doc.go +++ b/vendor/github.com/go-openapi/swag/stringutils/doc.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 // Package stringutils exposes helpers to search and process strings. package stringutils diff --git a/vendor/github.com/go-openapi/swag/stringutils/strings.go b/vendor/github.com/go-openapi/swag/stringutils/strings.go index 086592317..cd792b7d0 100644 --- a/vendor/github.com/go-openapi/swag/stringutils/strings.go +++ b/vendor/github.com/go-openapi/swag/stringutils/strings.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package stringutils diff --git a/vendor/github.com/go-openapi/swag/stringutils_iface.go b/vendor/github.com/go-openapi/swag/stringutils_iface.go index 00d7e0212..dbfa48484 100644 --- a/vendor/github.com/go-openapi/swag/stringutils_iface.go +++ b/vendor/github.com/go-openapi/swag/stringutils_iface.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package swag diff --git a/vendor/github.com/go-openapi/swag/typeutils/doc.go b/vendor/github.com/go-openapi/swag/typeutils/doc.go index 67e49d12e..66bed20df 100644 --- a/vendor/github.com/go-openapi/swag/typeutils/doc.go +++ b/vendor/github.com/go-openapi/swag/typeutils/doc.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 // Package typeutils exposes utilities to inspect generic types. package typeutils diff --git a/vendor/github.com/go-openapi/swag/typeutils/types.go b/vendor/github.com/go-openapi/swag/typeutils/types.go index 8a29aa9c2..55487a673 100644 --- a/vendor/github.com/go-openapi/swag/typeutils/types.go +++ b/vendor/github.com/go-openapi/swag/typeutils/types.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package typeutils @@ -22,11 +11,18 @@ type zeroable interface { // IsZero returns true when the value passed into the function is a zero value. // This allows for safer checking of interface values. -func IsZero(data interface{}) bool { +func IsZero(data any) bool { v := reflect.ValueOf(data) // check for nil data switch v.Kind() { //nolint:exhaustive - case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + case + reflect.Interface, + reflect.Func, + reflect.Chan, + reflect.Pointer, + reflect.UnsafePointer, + reflect.Map, + reflect.Slice: if v.IsNil() { return true } @@ -57,3 +53,28 @@ func IsZero(data interface{}) bool { return false } } + +// IsNil checks if input is nil. +// +// For types chan, func, interface, map, pointer, or slice it returns true if its argument is nil. +// +// See [reflect.Value.IsNil]. +func IsNil(input any) bool { + if input == nil { + return true + } + + kind := reflect.TypeOf(input).Kind() + switch kind { //nolint:exhaustive + case reflect.Pointer, + reflect.UnsafePointer, + reflect.Map, + reflect.Slice, + reflect.Chan, + reflect.Interface, + reflect.Func: + return reflect.ValueOf(input).IsNil() + default: + return false + } +} diff --git a/vendor/github.com/go-openapi/swag/typeutils_iface.go b/vendor/github.com/go-openapi/swag/typeutils_iface.go index b104a8040..b63813ea4 100644 --- a/vendor/github.com/go-openapi/swag/typeutils_iface.go +++ b/vendor/github.com/go-openapi/swag/typeutils_iface.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package swag @@ -20,4 +9,4 @@ import "github.com/go-openapi/swag/typeutils" // This allows for safer checking of interface values. // // Deprecated: use [typeutils.IsZero] instead. -func IsZero(data interface{}) bool { return typeutils.IsZero(data) } +func IsZero(data any) bool { return typeutils.IsZero(data) } diff --git a/vendor/github.com/go-openapi/swag/yamlutils/doc.go b/vendor/github.com/go-openapi/swag/yamlutils/doc.go index c8454c95f..7bb92a82f 100644 --- a/vendor/github.com/go-openapi/swag/yamlutils/doc.go +++ b/vendor/github.com/go-openapi/swag/yamlutils/doc.go @@ -1,16 +1,13 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 // Package yamlutils provides utilities to work with YAML documents. +// +// - [BytesToYAMLDoc] to construct a [yaml.Node] document +// - [YAMLToJSON] to convert a [yaml.Node] document to JSON bytes +// - [YAMLMapSlice] to serialize and deserialize YAML with the order of keys maintained package yamlutils + +import ( + _ "go.yaml.in/yaml/v3" // for documentation purpose only +) diff --git a/vendor/github.com/go-openapi/swag/yamlutils/errors.go b/vendor/github.com/go-openapi/swag/yamlutils/errors.go index 014f227d9..e87bc5e8b 100644 --- a/vendor/github.com/go-openapi/swag/yamlutils/errors.go +++ b/vendor/github.com/go-openapi/swag/yamlutils/errors.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package yamlutils diff --git a/vendor/github.com/go-openapi/swag/yamlutils/ordered_map.go b/vendor/github.com/go-openapi/swag/yamlutils/ordered_map.go index c12bcc19b..3daf68dbb 100644 --- a/vendor/github.com/go-openapi/swag/yamlutils/ordered_map.go +++ b/vendor/github.com/go-openapi/swag/yamlutils/ordered_map.go @@ -1,23 +1,32 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + package yamlutils import ( "fmt" - "reflect" + "iter" + "slices" "sort" "strconv" + "github.com/go-openapi/swag/conv" "github.com/go-openapi/swag/jsonutils" - yaml "gopkg.in/yaml.v3" + "github.com/go-openapi/swag/jsonutils/adapters/ifaces" + "github.com/go-openapi/swag/typeutils" + yaml "go.yaml.in/yaml/v3" ) var ( - _ yaml.Marshaler = YAMLMapSlice{} - // _ yaml.Unmarshaler = &YAMLMapSlice{} // TODO: implement yaml.Unmarshaler + _ yaml.Marshaler = YAMLMapSlice{} + _ yaml.Unmarshaler = &YAMLMapSlice{} ) // YAMLMapSlice represents a YAML object, with the order of keys maintained. // // It is similar to [jsonutils.JSONMapSlice] and also knows how to marshal and unmarshal YAML. +// +// It behaves like an ordered map, but keys can't be accessed in constant time. type YAMLMapSlice []YAMLMapItem // YAMLMapItem represents the value of a key in a YAML object held by [YAMLMapSlice]. @@ -26,12 +35,68 @@ type YAMLMapSlice []YAMLMapItem // you should not Marshal or Unmarshal directly this type, outside of a [YAMLMapSlice]. type YAMLMapItem = jsonutils.JSONMapItem +func (s YAMLMapSlice) OrderedItems() iter.Seq2[string, any] { + return func(yield func(string, any) bool) { + for _, item := range s { + if !yield(item.Key, item.Value) { + return + } + } + } +} + +// SetOrderedItems implements [ifaces.SetOrdered]: it merges keys passed by the iterator argument +// into the [YAMLMapSlice]. +func (s *YAMLMapSlice) SetOrderedItems(items iter.Seq2[string, any]) { + if items == nil { + // force receiver to be a nil slice + *s = nil + + return + } + + m := *s + if len(m) > 0 { + // update mode: short-circuited when unmarshaling fresh data structures + idx := make(map[string]int, len(m)) + + for i, item := range m { + idx[item.Key] = i + } + + for k, v := range items { + idx, ok := idx[k] + if ok { + m[idx].Value = v + + continue + } + + m = append(m, YAMLMapItem{Key: k, Value: v}) + } + + *s = m + + return + } + + for k, v := range items { + m = append(m, YAMLMapItem{Key: k, Value: v}) + } + + *s = m +} + // MarshalJSON renders this YAML object as JSON bytes. +// +// The difference with standard JSON marshaling is that the order of keys is maintained. func (s YAMLMapSlice) MarshalJSON() ([]byte, error) { return jsonutils.JSONMapSlice(s).MarshalJSON() } // UnmarshalJSON builds this YAML object from JSON bytes. +// +// The difference with standard JSON marshaling is that the order of keys is maintained. func (s *YAMLMapSlice) UnmarshalJSON(data []byte) error { js := jsonutils.JSONMapSlice(*s) @@ -45,7 +110,14 @@ func (s *YAMLMapSlice) UnmarshalJSON(data []byte) error { } // MarshalYAML produces a YAML document as bytes -func (s YAMLMapSlice) MarshalYAML() (interface{}, error) { +// +// The difference with standard YAML marshaling is that the order of keys is maintained. +// +// It implements [yaml.Marshaler]. +func (s YAMLMapSlice) MarshalYAML() (any, error) { + if typeutils.IsNil(s) { + return []byte("null\n"), nil + } var n yaml.Node n.Kind = yaml.DocumentNode var nodes []*yaml.Node @@ -77,30 +149,45 @@ func (s YAMLMapSlice) MarshalYAML() (interface{}, error) { return yaml.Marshal(&n) } -/* // UnmarshalYAML builds a YAMLMapSlice object from a YAML document [yaml.Node]. -func (s *YAMLMapSlice) UnmarshalYAML(value *yaml.Node) error { - panic("not implemented") +// +// It implements [yaml.Unmarshaler]. +func (s *YAMLMapSlice) UnmarshalYAML(node *yaml.Node) error { + if typeutils.IsNil(*s) { + // allow to unmarshal with a simple var declaration (nil slice) + *s = YAMLMapSlice{} + } + if node == nil { + *s = nil + return nil + } - return nil -} -*/ + const sensibleAllocDivider = 2 + m := slices.Grow(*s, len(node.Content)/sensibleAllocDivider) + m = m[:0] -func isNil(input interface{}) bool { - if input == nil { - return true - } - kind := reflect.TypeOf(input).Kind() - switch kind { //nolint:exhaustive - case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Chan: - return reflect.ValueOf(input).IsNil() - default: - return false + for i := 0; i < len(node.Content); i += 2 { + var nmi YAMLMapItem + k, err := yamlStringScalarC(node.Content[i]) + if err != nil { + return fmt.Errorf("unable to decode YAML map key: %w: %w", err, ErrYAML) + } + nmi.Key = k + v, err := yamlNode(node.Content[i+1]) + if err != nil { + return fmt.Errorf("unable to process YAML map value for key %q: %w: %w", k, err, ErrYAML) + } + nmi.Value = v + m = append(m, nmi) } + + *s = m + + return nil } -func json2yaml(item interface{}) (*yaml.Node, error) { - if isNil(item) { +func json2yaml(item any) (*yaml.Node, error) { + if typeutils.IsNil(item) { return &yaml.Node{ Kind: yaml.ScalarNode, Value: "null", @@ -108,39 +195,10 @@ func json2yaml(item interface{}) (*yaml.Node, error) { } switch val := item.(type) { - case YAMLMapSlice: - var n yaml.Node - n.Kind = yaml.MappingNode - for i := range val { - childNode, err := json2yaml(val[i].Value) - if err != nil { - return nil, err - } - n.Content = append(n.Content, &yaml.Node{ - Kind: yaml.ScalarNode, - Tag: yamlStringScalar, - Value: val[i].Key, - }, childNode) - } - return &n, nil - - case jsonutils.JSONMapSlice: - var n yaml.Node - n.Kind = yaml.MappingNode - for i := range val { - childNode, err := json2yaml(val[i].Value) - if err != nil { - return nil, err - } - n.Content = append(n.Content, &yaml.Node{ - Kind: yaml.ScalarNode, - Tag: yamlStringScalar, - Value: val[i].Key, - }, childNode) - } - return &n, nil + case ifaces.Ordered: + return orderedYAML(val) - case map[string]interface{}: + case map[string]any: var n yaml.Node n.Kind = yaml.MappingNode keys := make([]string, 0, len(val)) @@ -163,7 +221,7 @@ func json2yaml(item interface{}) (*yaml.Node, error) { } return &n, nil - case []interface{}: + case []any: var n yaml.Node n.Kind = yaml.SequenceNode for i := range val { @@ -180,24 +238,30 @@ func json2yaml(item interface{}) (*yaml.Node, error) { Tag: yamlStringScalar, Value: val, }, nil + case float32: + return floatNode(val) case float64: - return &yaml.Node{ - Kind: yaml.ScalarNode, - Tag: yamlFloatScalar, - Value: strconv.FormatFloat(val, 'f', -1, 64), - }, nil + return floatNode(val) + case int: + return integerNode(val) + case int8: + return integerNode(val) + case int16: + return integerNode(val) + case int32: + return integerNode(val) case int64: - return &yaml.Node{ - Kind: yaml.ScalarNode, - Tag: yamlIntScalar, - Value: strconv.FormatInt(val, 10), - }, nil + return integerNode(val) + case uint: + return uintegerNode(val) + case uint8: + return uintegerNode(val) + case uint16: + return uintegerNode(val) + case uint32: + return uintegerNode(val) case uint64: - return &yaml.Node{ - Kind: yaml.ScalarNode, - Tag: yamlIntScalar, - Value: strconv.FormatUint(val, 10), - }, nil + return uintegerNode(val) case bool: return &yaml.Node{ Kind: yaml.ScalarNode, @@ -208,3 +272,45 @@ func json2yaml(item interface{}) (*yaml.Node, error) { return nil, fmt.Errorf("unhandled type: %T: %w", val, ErrYAML) } } + +func floatNode[T conv.Float](val T) (*yaml.Node, error) { + return &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: yamlFloatScalar, + Value: conv.FormatFloat(val), + }, nil +} + +func integerNode[T conv.Signed](val T) (*yaml.Node, error) { + return &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: yamlIntScalar, + Value: conv.FormatInteger(val), + }, nil +} + +func uintegerNode[T conv.Unsigned](val T) (*yaml.Node, error) { + return &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: yamlIntScalar, + Value: conv.FormatUinteger(val), + }, nil +} + +func orderedYAML[T ifaces.Ordered](val T) (*yaml.Node, error) { + var n yaml.Node + n.Kind = yaml.MappingNode + for key, value := range val.OrderedItems() { + childNode, err := json2yaml(value) + if err != nil { + return nil, err + } + + n.Content = append(n.Content, &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: yamlStringScalar, + Value: key, + }, childNode) + } + return &n, nil +} diff --git a/vendor/github.com/go-openapi/swag/yamlutils/yaml.go b/vendor/github.com/go-openapi/swag/yamlutils/yaml.go index c7e88255f..e3aff3c2f 100644 --- a/vendor/github.com/go-openapi/swag/yamlutils/yaml.go +++ b/vendor/github.com/go-openapi/swag/yamlutils/yaml.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package yamlutils @@ -20,13 +9,15 @@ import ( "strconv" "github.com/go-openapi/swag/jsonutils" - yaml "gopkg.in/yaml.v3" + yaml "go.yaml.in/yaml/v3" ) // YAMLToJSON converts a YAML document into JSON bytes. // // Note: a YAML document is the output from a [yaml.Marshaler], e.g a pointer to a [yaml.Node]. -func YAMLToJSON(value interface{}) (json.RawMessage, error) { +// +// [YAMLToJSON] is typically called after [BytesToYAMLDoc]. +func YAMLToJSON(value any) (json.RawMessage, error) { jm, err := transformData(value) if err != nil { return nil, err @@ -42,7 +33,7 @@ func YAMLToJSON(value interface{}) (json.RawMessage, error) { // This function only supports root documents that are objects. // // A YAML document is a pointer to a [yaml.Node]. -func BytesToYAMLDoc(data []byte) (interface{}, error) { +func BytesToYAMLDoc(data []byte) (any, error) { var document yaml.Node // preserve order that is present in the document if err := yaml.Unmarshal(data, &document); err != nil { return nil, err @@ -53,7 +44,7 @@ func BytesToYAMLDoc(data []byte) (interface{}, error) { return &document, nil } -func yamlNode(root *yaml.Node) (interface{}, error) { +func yamlNode(root *yaml.Node) (any, error) { switch root.Kind { case yaml.DocumentNode: return yamlDocument(root) @@ -70,41 +61,28 @@ func yamlNode(root *yaml.Node) (interface{}, error) { } } -func yamlDocument(node *yaml.Node) (interface{}, error) { +func yamlDocument(node *yaml.Node) (any, error) { if len(node.Content) != 1 { return nil, fmt.Errorf("unexpected YAML Document node content length: %d: %w", len(node.Content), ErrYAML) } return yamlNode(node.Content[0]) } -func yamlMapping(node *yaml.Node) (interface{}, error) { - const sensibleAllocDivider = 2 +func yamlMapping(node *yaml.Node) (any, error) { + const sensibleAllocDivider = 2 // nodes concatenate (key,value) sequences m := make(YAMLMapSlice, len(node.Content)/sensibleAllocDivider) - var j int - for i := 0; i < len(node.Content); i += 2 { - var nmi YAMLMapItem - k, err := yamlStringScalarC(node.Content[i]) - if err != nil { - return nil, fmt.Errorf("unable to decode YAML map key: %w: %w", err, ErrYAML) - } - nmi.Key = k - v, err := yamlNode(node.Content[i+1]) - if err != nil { - return nil, fmt.Errorf("unable to process YAML map value for key %q: %w: %w", k, err, ErrYAML) - } - nmi.Value = v - m[j] = nmi - j++ + if err := m.UnmarshalYAML(node); err != nil { + return nil, err } + return m, nil } -func yamlSequence(node *yaml.Node) (interface{}, error) { - s := make([]interface{}, 0) - - for i := 0; i < len(node.Content); i++ { +func yamlSequence(node *yaml.Node) (any, error) { + s := make([]any, 0) + for i := range len(node.Content) { v, err := yamlNode(node.Content[i]) if err != nil { return nil, fmt.Errorf("unable to decode YAML sequence value: %w: %w", err, ErrYAML) @@ -123,7 +101,7 @@ const ( // See https://yaml.org/type/ yamlNull = "tag:yaml.org,2002:null" ) -func yamlScalar(node *yaml.Node) (interface{}, error) { +func yamlScalar(node *yaml.Node) (any, error) { switch node.LongTag() { case yamlStringScalar: return node.Value, nil @@ -167,42 +145,42 @@ func yamlStringScalarC(node *yaml.Node) (string, error) { } } -func transformData(input interface{}) (out interface{}, err error) { - format := func(t interface{}) (string, error) { - switch k := t.(type) { - case string: - return k, nil - case uint: - return strconv.FormatUint(uint64(k), 10), nil - case uint8: - return strconv.FormatUint(uint64(k), 10), nil - case uint16: - return strconv.FormatUint(uint64(k), 10), nil - case uint32: - return strconv.FormatUint(uint64(k), 10), nil - case uint64: - return strconv.FormatUint(k, 10), nil - case int: - return strconv.Itoa(k), nil - case int8: - return strconv.FormatInt(int64(k), 10), nil - case int16: - return strconv.FormatInt(int64(k), 10), nil - case int32: - return strconv.FormatInt(int64(k), 10), nil - case int64: - return strconv.FormatInt(k, 10), nil - default: - return "", fmt.Errorf("unexpected map key type, got: %T: %w", k, ErrYAML) - } +func format(t any) (string, error) { + switch k := t.(type) { + case string: + return k, nil + case uint: + return strconv.FormatUint(uint64(k), 10), nil + case uint8: + return strconv.FormatUint(uint64(k), 10), nil + case uint16: + return strconv.FormatUint(uint64(k), 10), nil + case uint32: + return strconv.FormatUint(uint64(k), 10), nil + case uint64: + return strconv.FormatUint(k, 10), nil + case int: + return strconv.Itoa(k), nil + case int8: + return strconv.FormatInt(int64(k), 10), nil + case int16: + return strconv.FormatInt(int64(k), 10), nil + case int32: + return strconv.FormatInt(int64(k), 10), nil + case int64: + return strconv.FormatInt(k, 10), nil + default: + return "", fmt.Errorf("unexpected map key type, got: %T: %w", k, ErrYAML) } +} +func transformData(input any) (out any, err error) { switch in := input.(type) { case yaml.Node: return yamlNode(&in) case *yaml.Node: return yamlNode(in) - case map[interface{}]interface{}: + case map[any]any: o := make(YAMLMapSlice, 0, len(in)) for ke, va := range in { var nmi YAMLMapItem @@ -218,10 +196,10 @@ func transformData(input interface{}) (out interface{}, err error) { o = append(o, nmi) } return o, nil - case []interface{}: + case []any: len1 := len(in) - o := make([]interface{}, len1) - for i := 0; i < len1; i++ { + o := make([]any, len1) + for i := range len1 { o[i], err = transformData(in[i]) if err != nil { return nil, err diff --git a/vendor/github.com/go-openapi/swag/yamlutils_iface.go b/vendor/github.com/go-openapi/swag/yamlutils_iface.go index 49e646486..57767efc5 100644 --- a/vendor/github.com/go-openapi/swag/yamlutils_iface.go +++ b/vendor/github.com/go-openapi/swag/yamlutils_iface.go @@ -1,16 +1,5 @@ -// Copyright 2015 go-swagger maintainers -// -// 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. +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 package swag @@ -23,9 +12,9 @@ import ( // YAMLToJSON converts YAML unmarshaled data into json compatible data // // Deprecated: use [yamlutils.YAMLToJSON] instead. -func YAMLToJSON(data interface{}) (json.RawMessage, error) { return yamlutils.YAMLToJSON(data) } +func YAMLToJSON(data any) (json.RawMessage, error) { return yamlutils.YAMLToJSON(data) } // BytesToYAMLDoc converts a byte slice into a YAML document // // Deprecated: use [yamlutils.BytesToYAMLDoc] instead. -func BytesToYAMLDoc(data []byte) (interface{}, error) { return yamlutils.BytesToYAMLDoc(data) } +func BytesToYAMLDoc(data []byte) (any, error) { return yamlutils.BytesToYAMLDoc(data) } diff --git a/vendor/github.com/google/cel-go/cel/BUILD.bazel b/vendor/github.com/google/cel-go/cel/BUILD.bazel index c12e4904d..62a56036a 100644 --- a/vendor/github.com/google/cel-go/cel/BUILD.bazel +++ b/vendor/github.com/google/cel-go/cel/BUILD.bazel @@ -10,6 +10,7 @@ go_library( "cel.go", "decls.go", "env.go", + "fieldpaths.go", "folding.go", "inlining.go", "io.go", @@ -21,10 +22,11 @@ go_library( "prompt.go", "validator.go", ], - embedsrcs = ["//cel/templates"], + embedsrcs = ["templates/authoring.tmpl"], importpath = "github.com/google/cel-go/cel", visibility = ["//visibility:public"], deps = [ + "//cel/async:go_default_library", "//checker:go_default_library", "//checker/decls:go_default_library", "//common:go_default_library", @@ -43,6 +45,7 @@ go_library( "//interpreter:go_default_library", "//parser:go_default_library", "@dev_cel_expr//:expr", + "@dev_cel_expr//conformance/proto3:go_default_library", "@org_golang_google_genproto_googleapis_api//expr/v1alpha1:go_default_library", "@org_golang_google_protobuf//proto:go_default_library", "@org_golang_google_protobuf//reflect/protodesc:go_default_library", @@ -63,10 +66,12 @@ go_test( "cel_test.go", "decls_test.go", "env_test.go", + "fieldpaths_test.go", "folding_test.go", "inlining_test.go", "io_test.go", "optimizer_test.go", + "program_async_test.go", "prompt_test.go", "validator_test.go", ], @@ -78,8 +83,10 @@ go_test( ], embedsrcs = [ "//cel/testdata:prompts", + "//cel/testdata:test_fds_with_source_info", ], deps = [ + "//cel/async:go_default_library", "//common/operators:go_default_library", "//common/overloads:go_default_library", "//common/types:go_default_library", @@ -89,6 +96,7 @@ go_test( "//test:go_default_library", "//test/proto2pb:go_default_library", "//test/proto3pb:go_default_library", + "@com_github_google_go_cmp//cmp:go_default_library", "@org_golang_google_genproto_googleapis_api//expr/v1alpha1:go_default_library", "@org_golang_google_protobuf//encoding/prototext:go_default_library", "@org_golang_google_protobuf//proto:go_default_library", @@ -96,3 +104,8 @@ go_test( "@org_golang_google_protobuf//types/known/wrapperspb:go_default_library", ], ) + +exports_files( + ["templates/authoring.tmpl"], + visibility = ["//visibility:public"], +) diff --git a/vendor/github.com/google/cel-go/cel/async/BUILD.bazel b/vendor/github.com/google/cel-go/cel/async/BUILD.bazel new file mode 100644 index 000000000..85b28bcdb --- /dev/null +++ b/vendor/github.com/google/cel-go/cel/async/BUILD.bazel @@ -0,0 +1,35 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +package( + licenses = ["notice"], # Apache 2.0 +) + +go_library( + name = "go_default_library", + srcs = [ + "async.go", + ], + importpath = "github.com/google/cel-go/cel/async", + visibility = ["//visibility:public"], + deps = [ + "//common/decls:go_default_library", + "//common/functions:go_default_library", + "//common/types:go_default_library", + "//common/types/ref:go_default_library", + "//interpreter:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "async_test.go", + ], + deps = [ + ":go_default_library", + "//common/decls:go_default_library", + "//common/functions:go_default_library", + "//common/types:go_default_library", + "//common/types/ref:go_default_library", + ], +) diff --git a/vendor/github.com/google/cel-go/cel/async/async.go b/vendor/github.com/google/cel-go/cel/async/async.go new file mode 100644 index 000000000..a011114bd --- /dev/null +++ b/vendor/github.com/google/cel-go/cel/async/async.go @@ -0,0 +1,235 @@ +// Copyright 2026 Google LLC +// +// 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 async provides helpers for configuring and executing asynchronous CEL functions, +// including drain strategies, retry, timeout, concurrency limiting, and caching wrappers. +package async + +import ( + "context" + "errors" + "time" + + "github.com/google/cel-go/common/decls" + "github.com/google/cel-go/common/functions" + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" + "github.com/google/cel-go/interpreter" +) + +// Call describes a pending or completed asynchronous function call. +// This interface exposes a safe, read-only view of the internal interpreter state. +type Call = interpreter.AsyncCall + +// Observer provides callbacks for monitoring the lifecycle of asynchronous function calls. +// +// Implementations must be safe for concurrent use: the start and finish callbacks run on different +// goroutines, and finish callbacks for distinct calls may run concurrently. See +// interpreter.AsyncObserver for details. +type Observer = interpreter.AsyncObserver + +// BlockingOp is a blocking asynchronous function operation. +type BlockingOp = functions.BlockingAsyncOp + +// DrainAction dictates what ConcurrentEval should do after inspecting completions. +type DrainAction struct { + // Reevaluate indicates that the AST should be re-evaluated immediately. + // If true, WaitDuration is ignored. + Reevaluate bool + // WaitDuration indicates how long the evaluator should wait for additional + // completions before deciding to re-evaluate. A duration of 0 means wait + // indefinitely (block on the next completion). + WaitDuration time.Duration +} + +// DrainStrategy controls when ConcurrentEval re-evaluates after async completions. +// +// The evaluator consults the strategy each time a completion is received. +type DrainStrategy interface { + // NextAction evaluates the current state of asynchronous evaluation and + // determines the next step. + // + // - completed: The set of completions accumulated in the current batch. + // - active: The number of async calls currently launched but unresolved. + NextAction(completed []Call, active int) DrainAction +} + +// DrainNone returns a strategy that re-evaluates after every single completion. +// This is the default strategy. +func DrainNone() DrainStrategy { + return drainNone{} +} + +type drainNone struct{} + +func (drainNone) NextAction(completed []Call, active int) DrainAction { + return DrainAction{Reevaluate: active == 0 || len(completed) > 0} +} + +// DrainReady returns a strategy that waits for a short duration after the first +// completion to batch any other functions that complete at roughly the same time. +func DrainReady(debounce time.Duration) DrainStrategy { + return drainReady{debounce: debounce} +} + +type drainReady struct { + debounce time.Duration +} + +func (d drainReady) NextAction(completed []Call, active int) DrainAction { + if active == 0 { + return DrainAction{Reevaluate: true} // Nothing left to wait for + } + if len(completed) == 0 { + return DrainAction{Reevaluate: false, WaitDuration: 0} // Wait indefinitely for first + } + return DrainAction{Reevaluate: false, WaitDuration: d.debounce} // Wait for debounce period +} + +// DrainAll returns a strategy that waits for all currently pending calls to +// complete before re-evaluating. +// +// Note: This strategy is optimal for independent async calls, but will over-wait +// if some calls depend on the results of others. +func DrainAll() DrainStrategy { + return drainAll{} +} + +type drainAll struct{} + +func (drainAll) NextAction(completed []Call, active int) DrainAction { + return DrainAction{Reevaluate: active == 0} +} + +// Timeout wraps a BlockingAsyncOp with a per-call timeout. +// +// The timeout is enforced even when the wrapped function ignores its context: the function runs on +// its own goroutine and Timeout selects on the deadline, returning a timeout error when it +// fires. A function that ignores cancellation cannot be forcibly stopped (Go cannot kill a +// goroutine), so its goroutine continues running in the background until it returns on its own; +// only its result is abandoned. This is the recommended way to bound functions that may hang or +// are not under the caller's control. The extra goroutine is incurred only by Timeout-wrapped +// calls, not by async evaluation in general. +func Timeout(fn functions.BlockingAsyncOp, timeout time.Duration) functions.BlockingAsyncOp { + return func(ctx context.Context, args ...ref.Val) ref.Val { + tCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + resCh := make(chan ref.Val, 1) + go func() { resCh <- fn(tCtx, args...) }() + select { + case res := <-resCh: + return res + case <-tCtx.Done(): + return types.NewErr("operation timed out after %v: %v", timeout, tCtx.Err()) + } + } +} + +// TimeoutBinding wraps a BlockingAsyncOp with a per-call timeout and returns an OverloadOpt. +func TimeoutBinding(fn functions.BlockingAsyncOp, timeout time.Duration) decls.OverloadOpt { + return decls.AsyncBinding(Timeout(fn, timeout)) +} + +// RetryOption configures the behavior of RetryBinding. +type RetryOption func(*retryConfig) + +type retryConfig struct { + maxAttempts int + backoff time.Duration +} + +// RetryAttempts sets the maximum number of attempts (including the first one). +func RetryAttempts(attempts int) RetryOption { + return func(c *retryConfig) { + c.maxAttempts = attempts + } +} + +// RetryBackoff sets the fixed backoff duration between attempts. +func RetryBackoff(backoff time.Duration) RetryOption { + return func(c *retryConfig) { + c.backoff = backoff + } +} + +// RetryableError is an interface that errors can implement to signal whether they are retryable. +type RetryableError interface { + error + IsRetryable() bool +} + +// Retry wraps a BlockingAsyncOp with a retry policy. +// It will retry the operation if it returns a types.Err that wraps a RetryableError returning true for IsRetryable. +func Retry(fn functions.BlockingAsyncOp, opts ...RetryOption) functions.BlockingAsyncOp { + config := &retryConfig{ + maxAttempts: 3, + backoff: 100 * time.Millisecond, + } + for _, opt := range opts { + opt(config) + } + + return func(ctx context.Context, args ...ref.Val) ref.Val { + var lastErr ref.Val + var backoff *time.Timer + defer func() { + if backoff != nil { + backoff.Stop() + } + }() + for i := 0; i < config.maxAttempts; i++ { + if i > 0 { + // Reuse a single timer across attempts and stop it on cancellation so the + // pending timer is not left to fire after the call returns. + if backoff == nil { + backoff = time.NewTimer(config.backoff) + } else { + backoff.Reset(config.backoff) + } + select { + case <-backoff.C: + case <-ctx.Done(): + backoff.Stop() + return types.NewErr("operation cancelled during retry: %v", ctx.Err()) + } + } + + res := fn(ctx, args...) + if !types.IsError(res) { + return res + } + + err := res.(*types.Err) + lastErr = res + + if !isRetryable(err) { + return res + } + } + return lastErr + } +} + +// RetryBinding wraps a BlockingAsyncOp with a retry policy and returns an OverloadOpt. +func RetryBinding(fn functions.BlockingAsyncOp, opts ...RetryOption) decls.OverloadOpt { + return decls.AsyncBinding(Retry(fn, opts...)) +} + +func isRetryable(err *types.Err) bool { + var re RetryableError + if errors.As(err, &re) { + return re.IsRetryable() + } + return false +} diff --git a/vendor/github.com/google/cel-go/cel/cel.go b/vendor/github.com/google/cel-go/cel/cel.go index eb5a9f4cc..9ba957a7e 100644 --- a/vendor/github.com/google/cel-go/cel/cel.go +++ b/vendor/github.com/google/cel-go/cel/cel.go @@ -17,3 +17,24 @@ // CEL is a non-Turing complete expression language designed to parse, check, and evaluate // expressions against user-defined environments. package cel + +// Compile is a convenience function that constructs a new Env using the provided EnvOption values, +// compiles the expression string, and plans an executable Program. +// +// Warning: Creating a new environment for every compilation is expensive. Environment setup should be done once +// and shared across expression compilations when the options remain the same. +func Compile(expression string, opts ...EnvOption) (Program, error) { + env, err := NewEnv(opts...) + if err != nil { + return nil, err + } + ast, iss := env.Compile(expression) + if iss.Err() != nil { + return nil, iss.Err() + } + prg, err := env.Program(ast, EvalOptions(OptOptimize)) + if err != nil { + return nil, err + } + return prg, nil +} diff --git a/vendor/github.com/google/cel-go/cel/decls.go b/vendor/github.com/google/cel-go/cel/decls.go index 4d4873bd6..53941146d 100644 --- a/vendor/github.com/google/cel-go/cel/decls.go +++ b/vendor/github.com/google/cel-go/cel/decls.go @@ -220,7 +220,11 @@ func ExcludeOverloads(overloadIDs ...string) OverloadSelector { // FunctionDecls provides one or more fully formed function declarations to be added to the environment. func FunctionDecls(funcs ...*decls.FunctionDecl) EnvOption { return func(e *Env) (*Env, error) { + if len(funcs) == 0 { + return e, nil + } var err error + e.ensureMutableFunctions() for _, fn := range funcs { if existing, found := e.functions[fn.Name()]; found { fn, err = existing.Merge(fn) @@ -346,6 +350,32 @@ func LateFunctionBinding() OverloadOpt { return decls.LateFunctionBinding() } +// AsyncBinding provides the implementation of an asynchronous overload. The provided function +// is called in its own goroutine with the provided context. The function should block until +// the result is available, and the framework manages goroutine and channel lifecycle. +// +// This follows the same pattern used by gRPC-Go and other major Go frameworks where user +// code is synchronous and the framework manages concurrency. +// +// Context contract: the function MUST return promptly once its context is cancelled. The +// framework cannot forcibly terminate the goroutine running the function, so a function that +// ignores cancellation will leak its goroutine and hold a concurrency slot (see +// AsyncMaxConcurrency) until it returns on its own. For functions that may hang or that are not +// under your control, wrap them with async.TimeoutBinding to bound their runtime. +func AsyncBinding(fn functions.BlockingAsyncOp) OverloadOpt { + return decls.AsyncBinding(fn) +} + +// SingletonAsyncBinding creates a singleton async function definition from a blocking function, +// to be used with all function overloads. The provided function is called in its own goroutine +// with the provided context. +// +// Note, this approach works well if operand is expected to have a specific trait which it implements, +// e.g. traits.ContainerType. Otherwise, prefer per-overload async bindings. +func SingletonAsyncBinding(fn functions.BlockingAsyncOp, traits ...int) FunctionOpt { + return decls.SingletonAsyncBinding(fn, traits...) +} + // OverloadIsNonStrict enables the function to be called with error and unknown argument values. // // Note: do not use this option unless absoluately necessary as it should be an uncommon feature. diff --git a/vendor/github.com/google/cel-go/cel/env.go b/vendor/github.com/google/cel-go/cel/env.go index bb3014464..794c62f78 100644 --- a/vendor/github.com/google/cel-go/cel/env.go +++ b/vendor/github.com/google/cel-go/cel/env.go @@ -17,7 +17,10 @@ package cel import ( "errors" "fmt" + "maps" "math" + "slices" + "strings" "sync" "github.com/google/cel-go/checker" @@ -27,6 +30,7 @@ import ( "github.com/google/cel-go/common/containers" "github.com/google/cel-go/common/decls" "github.com/google/cel-go/common/env" + "github.com/google/cel-go/common/functions" "github.com/google/cel-go/common/stdlib" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" @@ -45,6 +49,10 @@ type Source = common.Source type Ast struct { source Source impl *celast.AST + // loadErr captures an error detected while loading the AST (e.g. an over-deep AST ingested via + // ParsedExprToAst / CheckedExprToAst) so it can be surfaced when the Ast is checked or planned + // instead of recursing into the checker or planner on adversarially deep input. + loadErr error } // NativeRep converts the AST to a Go-native representation. @@ -138,10 +146,30 @@ type Env struct { provider types.Provider features map[int]bool appliedFeatures map[int]bool + limits map[limitID]int libraries map[string]SingletonLibrary validators []ASTValidator costOptions []checker.CostOption + // Flags for copy-on-write behavior with env.Extend. + funcsShared bool + featuresShared bool + appliedFeaturesShared bool + limitsShared bool + libsShared bool + + parent *Env + + // sharedDispatcher caches a dispatcher populated with the env's function + // bindings, built once and reused across every Program() constructed from + // this env. It is read-only after construction; each Program layers a thin + // child over it for per-program Functions(). Extended envs reuse the parent's + // dispatcher if functions are unchanged. + sharedDispatcher interpreter.Dispatcher + dispOnce sync.Once + hasAsync bool + dispErr error + // Internal parser representation prsr *parser.Parser prsrOpts []parser.Option @@ -177,6 +205,16 @@ func (e *Env) ToConfig(name string) (*env.Config, error) { conf.AddImports(env.NewImport(typeName)) } + // Serialize features + for featID, enabled := range e.features { + featName, found := featureNameByID(featID) + if !found { + // If the feature isn't named, it isn't intended to be publicly exposed + continue + } + conf.AddFeatures(env.NewFeature(featName, enabled)) + } + libOverloads := map[string][]string{} for libName, lib := range e.libraries { // Track the options which have been configured by a library and @@ -237,7 +275,7 @@ func (e *Env) ToConfig(name string) (*env.Config, error) { fields := e.contextProto.Fields() for i := 0; i < fields.Len(); i++ { field := fields.Get(i) - variable, err := fieldToVariable(field) + variable, err := fieldToVariable(field, e.HasFeature(featureJSONFieldNames)) if err != nil { return nil, fmt.Errorf("could not serialize context field variable %q, reason: %w", field.FullName(), err) } @@ -272,16 +310,45 @@ func (e *Env) ToConfig(name string) (*env.Config, error) { } } - // Serialize features - for featID, enabled := range e.features { - featName, found := featureNameByID(featID) - if !found { - // If the feature isn't named, it isn't intended to be publicly exposed + for id, val := range e.limits { + limitName, found := limitNameByID(id) + if !found || val == 0 { + // skip if explicitly defaulted or not supported in config continue } - conf.AddFeatures(env.NewFeature(featName, enabled)) + conf.AddLimits(env.NewLimit(limitName, val)) } + // Sort repeated fields in config where reasonable to make the export + // stable. + slices.SortFunc(conf.Imports, func(a *env.Import, b *env.Import) int { + return strings.Compare(a.Name, b.Name) + }) + + slices.SortFunc(conf.Extensions, func(a *env.Extension, b *env.Extension) int { + return strings.Compare(a.Name, b.Name) + }) + + slices.SortFunc(conf.Variables, func(a *env.Variable, b *env.Variable) int { + return strings.Compare(a.Name, b.Name) + }) + + slices.SortFunc(conf.Functions, func(a *env.Function, b *env.Function) int { + return strings.Compare(a.Name, b.Name) + }) + + slices.SortFunc(conf.Validators, func(a *env.Validator, b *env.Validator) int { + return strings.Compare(a.Name, b.Name) + }) + + slices.SortFunc(conf.Features, func(a *env.Feature, b *env.Feature) int { + return strings.Compare(a.Name, b.Name) + }) + + slices.SortFunc(conf.Limits, func(a *env.Limit, b *env.Limit) int { + return strings.Compare(a.Name, b.Name) + }) + return conf, nil } @@ -328,6 +395,7 @@ func NewCustomEnv(opts ...EnvOption) (*Env, error) { provider: registry, features: map[int]bool{}, appliedFeatures: map[int]bool{}, + limits: map[limitID]int{}, libraries: map[string]SingletonLibrary{}, validators: []ASTValidator{}, progOpts: []ProgramOption{}, @@ -347,6 +415,20 @@ func NewCustomEnv(opts ...EnvOption) (*Env, error) { // It is possible to have both non-nil Ast and Issues values returned from this call: however, // the mere presence of an Ast does not imply that it is valid for use. func (e *Env) Check(ast *Ast) (*Ast, *Issues) { + // Surface any error recorded while the Ast was loaded (e.g. an over-deep AST rejected by + // ParsedExprToAst / CheckedExprToAst) before recursing into the type checker on it. + if ast != nil && ast.loadErr != nil { + errs := common.NewErrors(ast.Source()) + errs.ReportErrorString(common.NoLocation, ast.loadErr.Error()) + return nil, NewIssuesWithSourceInfo(errs, ast.NativeRep().SourceInfo()) + } + if nodeLimit := e.configuredExpressionNodeLimit(); nodeLimit > 0 && ast != nil && ast.NativeRep() != nil { + if count := celast.NodeCount(ast.NativeRep()); count > nodeLimit { + errs := common.NewErrors(ast.Source()) + errs.ReportErrorString(common.NoLocation, fmt.Sprintf("expression node count exceeds limit: count %d, limit %d", count, nodeLimit)) + return nil, NewIssuesWithSourceInfo(errs, ast.NativeRep().SourceInfo()) + } + } // Construct the internal checker env, erroring if there is an issue adding the declarations. chk, err := e.initChecker() if err != nil { @@ -388,6 +470,24 @@ func (e *Env) Check(ast *Ast) (*Ast, *Issues) { return ast, nil } +// configuredExpressionSizeLimit returns the effective expression size code point limit. +// A zero value means "use the parser default". +func (e *Env) configuredExpressionSizeLimit() int { + if l := e.limits[limitCodePointSize]; l != 0 { + return l + } + return 100_000 +} + +// configuredExpressionNodeLimit returns the effective expression node limit. +// A zero value means "use default". +func (e *Env) configuredExpressionNodeLimit() int { + if l := e.limits[limitExpressionNodeCount]; l != 0 { + return l + } + return 100_000 +} + // Compile combines the Parse and Check phases CEL program compilation to produce an Ast and // associated issues. // @@ -397,7 +497,11 @@ func (e *Env) Check(ast *Ast) (*Ast, *Issues) { // // Note, for parse-only uses of CEL use Parse. func (e *Env) Compile(txt string) (*Ast, *Issues) { - return e.CompileSource(common.NewTextSource(txt)) + src, err := common.NewTextSourceWithLimit(txt, e.configuredExpressionSizeLimit()) + if err != nil { + return nil, ErrorAsIssues(err) + } + return e.CompileSource(src) } // CompileSource combines the Parse and Check phases CEL program compilation to produce an Ast and @@ -428,34 +532,17 @@ func (e *Env) CompileSource(src Source) (*Ast, *Issues) { // TypeProvider are immutable, or that their underlying implementations are based on the // ref.TypeRegistry which provides a Copy method which will be invoked by this method. func (e *Env) Extend(opts ...EnvOption) (*Env, error) { - chk, chkErr := e.getCheckerOrError() - if chkErr != nil { + if _, chkErr := e.getCheckerOrError(); chkErr != nil { return nil, chkErr } - prsrOptsCopy := make([]parser.Option, len(e.prsrOpts)) - copy(prsrOptsCopy, e.prsrOpts) - - // The type-checker is configured with Declarations. The declarations may either be provided - // as options which have not yet been validated, or may come from a previous checker instance - // whose types have already been validated. - chkOptsCopy := make([]checker.Option, len(e.chkOpts)) - copy(chkOptsCopy, e.chkOpts) - - // Copy the declarations if needed. - if chk != nil { - // If the type-checker has already been instantiated, then the e.declarations have been - // validated within the chk instance. - chkOptsCopy = append(chkOptsCopy, checker.ValidatedDeclarations(chk)) - } - varsCopy := make([]*decls.VariableDecl, len(e.variables)) - copy(varsCopy, e.variables) - - // Copy macros and program options - macsCopy := make([]parser.Macro, len(e.macros)) - progOptsCopy := make([]ProgramOption, len(e.progOpts)) - copy(macsCopy, e.macros) - copy(progOptsCopy, e.progOpts) + prsrOptsCopy := slices.Clone(e.prsrOpts) + chkOptsCopy := slices.Clone(e.chkOpts) + varsCopy := slices.Clone(e.variables) + macsCopy := slices.Clone(e.macros) + progOptsCopy := slices.Clone(e.progOpts) + validatorsCopy := slices.Clone(e.validators) + costOptsCopy := slices.Clone(e.costOptions) // Copy the adapter / provider if they appear to be mutable. adapter := e.adapter @@ -484,47 +571,69 @@ func (e *Env) Extend(opts ...EnvOption) (*Env, error) { adapter = adapterReg.Copy() } - featuresCopy := make(map[int]bool, len(e.features)) - for k, v := range e.features { - featuresCopy[k] = v - } - appliedFeaturesCopy := make(map[int]bool, len(e.appliedFeatures)) - for k, v := range e.appliedFeatures { - appliedFeaturesCopy[k] = v - } - funcsCopy := make(map[string]*decls.FunctionDecl, len(e.functions)) - for k, v := range e.functions { - funcsCopy[k] = v - } - libsCopy := make(map[string]SingletonLibrary, len(e.libraries)) - for k, v := range e.libraries { - libsCopy[k] = v - } - validatorsCopy := make([]ASTValidator, len(e.validators)) - copy(validatorsCopy, e.validators) - costOptsCopy := make([]checker.CostOption, len(e.costOptions)) - copy(costOptsCopy, e.costOptions) - ext := &Env{ + parent: e, Container: e.Container, variables: varsCopy, - functions: funcsCopy, + functions: e.functions, macros: macsCopy, contextProto: e.contextProto, progOpts: progOptsCopy, adapter: adapter, - features: featuresCopy, - appliedFeatures: appliedFeaturesCopy, - libraries: libsCopy, + features: e.features, + limits: e.limits, + appliedFeatures: e.appliedFeatures, + libraries: e.libraries, validators: validatorsCopy, provider: provider, chkOpts: chkOptsCopy, prsrOpts: prsrOptsCopy, costOptions: costOptsCopy, + // Copy-on-write flags. + funcsShared: true, + featuresShared: true, + limitsShared: true, + appliedFeaturesShared: true, + libsShared: true, } return ext.configure(opts) } +func (e *Env) ensureMutableFunctions() { + if e.funcsShared { + e.functions = maps.Clone(e.functions) + e.funcsShared = false + } +} + +func (e *Env) ensureMutableLibraries() { + if e.libsShared { + e.libraries = maps.Clone(e.libraries) + e.libsShared = false + } +} + +func (e *Env) ensureMutableFeatures() { + if e.featuresShared { + e.features = maps.Clone(e.features) + e.featuresShared = false + } +} + +func (e *Env) ensureMutableAppliedFeatures() { + if e.appliedFeaturesShared { + e.appliedFeatures = maps.Clone(e.appliedFeatures) + e.appliedFeaturesShared = false + } +} + +func (e *Env) ensureMutableLimits() { + if e.limitsShared { + e.limits = maps.Clone(e.limits) + e.limitsShared = false + } +} + // HasFeature checks whether the environment enables the given feature // flag, as enumerated in options.go. func (e *Env) HasFeature(flag int) bool { @@ -555,25 +664,17 @@ func (e *Env) HasFunction(functionName string) bool { // Functions returns a shallow copy of the Functions, keyed by function name, that have been configured in the environment. func (e *Env) Functions() map[string]*decls.FunctionDecl { - shallowCopy := make(map[string]*decls.FunctionDecl, len(e.functions)) - for nm, fn := range e.functions { - shallowCopy[nm] = fn - } - return shallowCopy + return maps.Clone(e.functions) } // Variables returns a shallow copy of the variables associated with the environment. func (e *Env) Variables() []*decls.VariableDecl { - shallowCopy := make([]*decls.VariableDecl, len(e.variables)) - copy(shallowCopy, e.variables) - return shallowCopy + return slices.Clone(e.variables) } // Macros returns a shallow copy of macros associated with the environment. func (e *Env) Macros() []Macro { - shallowCopy := make([]Macro, len(e.macros)) - copy(shallowCopy, e.macros) - return shallowCopy + return slices.Clone(e.macros) } // HasValidator returns whether a specific ASTValidator has been configured in the environment. @@ -586,9 +687,9 @@ func (e *Env) HasValidator(name string) bool { return false } -// Validators returns the set of ASTValidators configured on the environment. +// Validators returns a shallow copy of the set of ASTValidators configured on the environment. func (e *Env) Validators() []ASTValidator { - return e.validators[:] + return slices.Clone(e.validators) } // Parse parses the input expression value `txt` to a Ast and/or a set of Issues. @@ -596,7 +697,10 @@ func (e *Env) Validators() []ASTValidator { // This form of Parse creates a Source value for the input `txt` and forwards to the // ParseSource method. func (e *Env) Parse(txt string) (*Ast, *Issues) { - src := common.NewTextSource(txt) + src, err := common.NewTextSourceWithLimit(txt, e.configuredExpressionSizeLimit()) + if err != nil { + return nil, ErrorAsIssues(err) + } return e.ParseSource(src) } @@ -617,6 +721,12 @@ func (e *Env) ParseSource(src Source) (*Ast, *Issues) { // Program generates an evaluable instance of the Ast within the environment (Env). func (e *Env) Program(ast *Ast, opts ...ProgramOption) (Program, error) { + // Surface any error recorded while the Ast was loaded (e.g. an over-deep AST rejected by + // ParsedExprToAst / CheckedExprToAst) rather than recursing into the planner on it. This is a + // cheap field read; the depth traversal itself runs once at conversion time, not here. + if ast != nil && ast.loadErr != nil { + return nil, ast.loadErr + } return e.PlanProgram(ast.NativeRep(), opts...) } @@ -633,6 +743,41 @@ func (e *Env) PlanProgram(a *celast.AST, opts ...ProgramOption) (Program, error) return newProgram(e, a, optSet) } +func (e *Env) initDispatcher() (interpreter.Dispatcher, bool, error) { + e.dispOnce.Do(func() { + if e.parent != nil && e.funcsShared { + // The dispatcher setup is skipped when the child has mutated the function set. + // As the child function set contains a copy of all parent function declarations + // by virtue of copy on write semantics. + d, hasAsync, err := e.parent.initDispatcher() + e.sharedDispatcher = d + e.hasAsync = hasAsync + e.dispErr = err + return + } + hasAsync := false + var bindings []*functions.Overload + for _, fn := range e.functions { + bs, err := fn.Bindings() + if err != nil { + e.dispErr = err + return + } + for _, b := range bs { + if b.Async != nil { + hasAsync = true + } + } + bindings = append(bindings, bs...) + } + d := interpreter.NewDispatcher() + e.dispErr = d.Add(bindings...) + e.sharedDispatcher = d + e.hasAsync = hasAsync + }) + return e.sharedDispatcher, e.hasAsync, e.dispErr +} + // CELTypeAdapter returns the `types.Adapter` configured for the environment. func (e *Env) CELTypeAdapter() types.Adapter { return e.adapter @@ -758,6 +903,7 @@ func (e *Env) configure(opts []EnvOption) (*Env, error) { // If the default UTC timezone has been disabled, configure the legacy overloads if utcTime, isSet := e.features[featureDefaultUTCTimeZone]; isSet && !utcTime { if !e.appliedFeatures[featureDefaultUTCTimeZone] { + e.ensureMutableAppliedFeatures() e.appliedFeatures[featureDefaultUTCTimeZone] = true e, err = Lib(timeLegacyLibrary{})(e) if err != nil { @@ -780,11 +926,35 @@ func (e *Env) configure(opts []EnvOption) (*Env, error) { if e.HasFeature(featureIdentEscapeSyntax) { prsrOpts = append(prsrOpts, parser.EnableIdentEscapeSyntax(true)) } + if l := e.limits[limitParseErrorRecovery]; l != 0 { + prsrOpts = append(prsrOpts, parser.ErrorRecoveryLimit(l)) + } + if l := e.limits[limitCodePointSize]; l != 0 { + prsrOpts = append(prsrOpts, parser.ExpressionSizeCodePointLimit(l)) + } + if l := e.limits[limitParseRecursionDepth]; l != 0 { + prsrOpts = append(prsrOpts, parser.MaxRecursionDepth(l)) + } + if l := e.limits[limitExpressionNodeCount]; l != 0 { + prsrOpts = append(prsrOpts, parser.MaxExpressionNodeCount(l)) + } e.prsr, err = parser.NewParser(prsrOpts...) if err != nil { return nil, err } + // Enable JSON field names is using a proto-based *types.Registry + if e.HasFeature(featureJSONFieldNames) { + reg, isReg := e.provider.(*types.Registry) + if !isReg { + return nil, fmt.Errorf("JSONFieldNames() option is only compatible with *types.Registry providers") + } + err := reg.WithJSONFieldNames(true) + if err != nil { + return nil, err + } + } + // Ensure that the checker init happens eagerly rather than lazily. if e.HasFeature(featureEagerlyValidateDeclarations) { _, err := e.initChecker() @@ -803,6 +973,17 @@ func (e *Env) initChecker() (*checker.Env, error) { chkOpts = append(chkOpts, checker.CrossTypeNumericComparisons( e.HasFeature(featureCrossTypeNumericComparisons))) + chkOpts = append(chkOpts, + checker.JSONFieldNames(e.HasFeature(featureJSONFieldNames))) + + if e.parent != nil && e.funcsShared { + parentChk, err := e.parent.initChecker() + if err != nil { + e.setCheckerOrError(nil, err) + return + } + chkOpts = append(chkOpts, checker.ValidatedDeclarations(parentChk)) + } ce, err := checker.NewEnv(e.Container, e.provider, chkOpts...) if err != nil { @@ -816,14 +997,16 @@ func (e *Env) initChecker() (*checker.Env, error) { return } // Add the function declarations which are derived from the FunctionDecl instances. - for _, fn := range e.functions { - if fn.IsDeclarationDisabled() { - continue - } - err = ce.AddFunctions(fn) - if err != nil { - e.setCheckerOrError(nil, err) - return + if e.parent == nil || !e.funcsShared { + for _, fn := range e.functions { + if fn.IsDeclarationDisabled() { + continue + } + err = ce.AddFunctions(fn) + if err != nil { + e.setCheckerOrError(nil, err) + return + } } } // Add function declarations here separately. @@ -872,6 +1055,16 @@ type Issues struct { info *celast.SourceInfo } +// ErrorAsIssues wraps a Golang error into a CEL common error and issue set. +// +// This is a convenience method for early returning from an expression validation call path due to +// internal state or configuration which is unrelated to the source being validated. +func ErrorAsIssues(err error) *Issues { + errs := common.NewErrors(common.NewTextSource("")) + errs.ReportErrorString(common.NoLocation, err.Error()) + return NewIssues(errs) +} + // NewIssues returns an Issues struct from a common.Errors object. func NewIssues(errs *common.Errors) *Issues { return NewIssuesWithSourceInfo(errs, nil) @@ -980,9 +1173,10 @@ func (p *interopCELTypeProvider) FindStructFieldType(structType, fieldName strin return nil, false } return &types.FieldType{ - Type: t, - IsSet: ft.IsSet, - GetFrom: ft.GetFrom, + Type: t, + IsSet: ft.IsSet, + GetFrom: ft.GetFrom, + IsJSONField: ft.IsJSONField, }, true } return nil, false diff --git a/vendor/github.com/google/cel-go/cel/fieldpaths.go b/vendor/github.com/google/cel-go/cel/fieldpaths.go new file mode 100644 index 000000000..570fce3a4 --- /dev/null +++ b/vendor/github.com/google/cel-go/cel/fieldpaths.go @@ -0,0 +1,163 @@ +package cel + +import ( + "slices" + "strings" + + "github.com/google/cel-go/common" + "github.com/google/cel-go/common/types" +) + +// fieldPath represents a selection path to a field from a variable in a CEL environment. +type fieldPath struct { + celType *Type + // path represents the selection path to the field. + path string + description string + isLeaf bool +} + +// Documentation implements the Documentor interface. +func (f *fieldPath) Documentation() *common.Doc { + return common.NewFieldDoc(f.path, f.celType.String(), f.description) +} + +type documentationProvider interface { + // FindStructFieldDescription returns documentation for a field if available. + // Returns false if the field could not be found. + FindStructFieldDescription(typeName, fieldName string) (string, bool) +} + +type backtrack struct { + // provider used to resolve types. + provider types.Provider + // paths of fields that have been visited along the path. + path []string + // types of fields that have been visited along the path. used to avoid cycles. + types []*Type +} + +func (b *backtrack) push(pathStep string, celType *Type) { + b.path = append(b.path, pathStep) + b.types = append(b.types, celType) +} + +func (b *backtrack) pop() { + b.path = b.path[:len(b.path)-1] + b.types = b.types[:len(b.types)-1] +} + +func formatPath(path []string) string { + var buffer strings.Builder + for i, p := range path { + if i == 0 { + buffer.WriteString(p) + continue + } + if strings.HasPrefix(p, "[") { + buffer.WriteString(p) + continue + } + buffer.WriteString(".") + buffer.WriteString(p) + } + return buffer.String() +} + +func (b *backtrack) expandFieldPaths(celType *Type, paths []*fieldPath) []*fieldPath { + if slices.ContainsFunc(b.types[:len(b.types)-1], func(t *Type) bool { return t.String() == celType.String() }) { + // Cycle detected, so stop expanding. + paths[len(paths)-1].isLeaf = false + return paths + } + switch celType.Kind() { + case types.StructKind: + fields, ok := b.provider.FindStructFieldNames(celType.String()) + if !ok { + // Caller added this type to the path, so it must be a leaf. + paths[len(paths)-1].isLeaf = true + return paths + } + for _, field := range fields { + fieldType, ok := b.provider.FindStructFieldType(celType.String(), field) + if !ok { + // Field not found, either hidden or an error. + continue + } + b.push(field, celType) + description := "" + if docProvider, ok := b.provider.(documentationProvider); ok { + description, _ = docProvider.FindStructFieldDescription(celType.String(), field) + } + path := &fieldPath{ + celType: fieldType.Type, + path: formatPath(b.path), + description: description, + isLeaf: false, + } + paths = append(paths, path) + paths = b.expandFieldPaths(fieldType.Type, paths) + b.pop() + } + return paths + case types.MapKind: + if len(celType.Parameters()) != 2 { + // dynamic map, so treat as a leaf. + paths[len(paths)-1].isLeaf = true + return paths + } + mapKeyType := celType.Parameters()[0] + mapValueType := celType.Parameters()[1] + // Add a placeholder for the map key kind (the zero value). + keyIdentifier := "" + switch mapKeyType.Kind() { + case types.StringKind: + keyIdentifier = "[\"\"]" + case types.IntKind: + keyIdentifier = "[0]" + case types.UintKind: + keyIdentifier = "[0u]" + case types.BoolKind: + keyIdentifier = "[false]" + default: + // Caller added this type to the path, so it must be a leaf. + paths[len(paths)-1].isLeaf = true + return paths + } + b.push(keyIdentifier, mapValueType) + defer b.pop() + return b.expandFieldPaths(mapValueType, paths) + case types.ListKind: + if len(celType.Parameters()) != 1 { + // dynamic list, so treat as a leaf. + paths[len(paths)-1].isLeaf = true + return paths + } + listElemType := celType.Parameters()[0] + b.push("[0]", listElemType) + defer b.pop() + return b.expandFieldPaths(listElemType, paths) + default: + paths[len(paths)-1].isLeaf = true + } + + return paths +} + +// fieldPathsForType expands the reachable fields from the given root identifier. +func fieldPathsForType(provider types.Provider, identifier string, celType *Type) []*fieldPath { + b := &backtrack{ + provider: provider, + path: []string{identifier}, + types: []*Type{celType}, + } + paths := []*fieldPath{ + { + celType: celType, + path: identifier, + isLeaf: false, + }, + } + + return b.expandFieldPaths(celType, paths) +} diff --git a/vendor/github.com/google/cel-go/cel/folding.go b/vendor/github.com/google/cel-go/cel/folding.go index 40d843ece..e3459034c 100644 --- a/vendor/github.com/google/cel-go/cel/folding.go +++ b/vendor/github.com/google/cel-go/cel/folding.go @@ -15,6 +15,8 @@ package cel import ( + "context" + "errors" "fmt" "github.com/google/cel-go/common/ast" @@ -38,7 +40,7 @@ func MaxConstantFoldIterations(limit int) ConstantFoldingOption { } } -// Adds an Activation which provides known values for the folding evaluator +// FoldKnownValues adds an Activation which provides known values for the folding evaluator // // Any values the activation provides will be used by the constant folder and turned into // literals in the AST. @@ -93,18 +95,18 @@ func (opt *constantFoldingOptimizer) Optimize(ctx *OptimizerContext, a *ast.AST) for _, fold := range foldableExprs { // If the expression could be folded because it's a non-strict call, and the // branches are pruned, continue to the next fold. - if fold.Kind() == ast.CallKind && maybePruneBranches(ctx, fold) { + if fold.Kind() == ast.CallKind && maybePruneBranches(ctx, a, fold) { continue } // Late-bound function calls cannot be folded. - if fold.Kind() == ast.CallKind && isLateBoundFunctionCall(ctx, a, fold) { + if fold.Kind() == ast.CallKind && isLateBoundFunctionCall(ctx, fold) { continue } // Otherwise, assume all context is needed to evaluate the expression. err := opt.tryFold(ctx, a, fold) - // Ignore errors for identifiers, since there is no guarantee that the environment + // Ignore errors for identifiers or subexpressions that cannot be folded, since there is no guarantee that the environment // has a value for them. - if err != nil && fold.Kind() != ast.IdentKind { + if err != nil && fold.Kind() != ast.IdentKind && !errors.Is(err, errCannotFold) { ctx.ReportErrorAtID(fold.ID(), "constant-folding evaluation failed: %v", err.Error()) return a } @@ -142,24 +144,19 @@ func (opt *constantFoldingOptimizer) Optimize(ctx *OptimizerContext, a *ast.AST) return a } +var errCannotFold = errors.New("subexpression cannot be folded") + // tryFold attempts to evaluate a sub-expression to a literal. // // If the evaluation succeeds, the input expr value will be modified to become a literal, otherwise // the method will return an error. func (opt *constantFoldingOptimizer) tryFold(ctx *OptimizerContext, a *ast.AST, expr ast.Expr) error { - // Assume all context is needed to evaluate the expression. - subAST := &Ast{ - impl: ast.NewCheckedAST(ast.NewAST(expr, a.SourceInfo()), a.TypeMap(), a.ReferenceMap()), - } - prg, err := ctx.Program(subAST) - if err != nil { - return err - } activation := opt.knownValues if activation == nil { activation = NoVars() } - out, _, err := prg.Eval(activation) + navExpr := expr.(ast.NavigableExpr) + out, err := evaluateExpr(ctx, a, navExpr, activation) if err != nil { return err } @@ -168,7 +165,31 @@ func (opt *constantFoldingOptimizer) tryFold(ctx *OptimizerContext, a *ast.AST, return nil } -func isLateBoundFunctionCall(ctx *OptimizerContext, a *ast.AST, expr ast.Expr) bool { +func evaluateExpr(ctx *OptimizerContext, a *ast.AST, navigableExpr ast.NavigableExpr, activation Activation) (ref.Val, error) { + partialActivation, err := ctx.PartialVars(activation) + if err != nil { + return nil, err + } + subAST := &Ast{ + impl: ast.NewCheckedAST(ast.NewAST(navigableExpr, a.SourceInfo()), a.TypeMap(), a.ReferenceMap()), + } + prg, err := ctx.Program(subAST) + if err != nil { + return nil, errCannotFold + } + // Folding will not attempt to call async functions which are all marked as late-bound, + // but the presence of such functions requires the use of `ConcurrentEval` in order to + // avoid an early return error which blocks async functions from running in `Eval` and + // `ContextEval` call paths. + resCh := prg.ConcurrentEval(context.Background(), partialActivation) + res := <-resCh + if res.Err != nil || types.IsUnknown(res.Val) { + return nil, errCannotFold + } + return res.Val, nil +} + +func isLateBoundFunctionCall(ctx *OptimizerContext, expr ast.Expr) bool { call := expr.AsCall() function := ctx.Functions()[call.FunctionName()] if function == nil { @@ -181,12 +202,12 @@ func isLateBoundFunctionCall(ctx *OptimizerContext, a *ast.AST, expr ast.Expr) b // a branch can be removed. Evaluation will naturally prune logical and / or calls, // but conditional will not be pruned cleanly, so this is one small area where the // constant folding step reimplements a portion of the evaluator. -func maybePruneBranches(ctx *OptimizerContext, expr ast.NavigableExpr) bool { +func maybePruneBranches(ctx *OptimizerContext, a *ast.AST, expr ast.NavigableExpr) bool { call := expr.AsCall() args := call.Args() switch call.FunctionName() { case operators.LogicalAnd, operators.LogicalOr: - return maybeShortcircuitLogic(ctx, call.FunctionName(), args, expr) + return maybeShortcircuitLogic(ctx, a, call.FunctionName(), args, expr) case operators.Conditional: cond := args[0] truthy := args[1] @@ -207,21 +228,47 @@ func maybePruneBranches(ctx *OptimizerContext, expr ast.NavigableExpr) bool { return true } needle := args[0] - if needle.Kind() == ast.LiteralKind && haystack.Kind() == ast.ListKind { - needleValue := needle.AsLiteral() + if (needle.Kind() == ast.LiteralKind || isSelfEqualIdent(needle)) && haystack.Kind() == ast.ListKind { + needleIsLit := needle.Kind() == ast.LiteralKind + needleLitVal := needle.AsLiteral() + needleIdentVal := needle.AsIdent() list := haystack.AsList() - for _, e := range list.Elements() { - if e.Kind() == ast.LiteralKind && e.AsLiteral().Equal(needleValue) == types.True { + for _, elem := range list.Elements() { + if needleIsLit && elem.Kind() == ast.LiteralKind && elem.AsLiteral().Equal(needleLitVal) == types.True { + ctx.UpdateExpr(expr, ctx.NewLiteral(types.True)) + return true + } + if !needleIsLit && elem.Kind() == ast.IdentKind && elem.AsIdent() == needleIdentVal { ctx.UpdateExpr(expr, ctx.NewLiteral(types.True)) return true } } } + case operators.Add: + if len(args) == 2 && args[0].Kind() == ast.ListKind && args[1].Kind() == ast.ListKind { + leftList := args[0].AsList() + rightList := args[1].AsList() + + elems := make([]ast.Expr, 0, leftList.Size()+rightList.Size()) + elems = append(elems, leftList.Elements()...) + elems = append(elems, rightList.Elements()...) + + optIndices := make([]int32, 0, len(leftList.OptionalIndices())+len(rightList.OptionalIndices())) + optIndices = append(optIndices, leftList.OptionalIndices()...) + offset := int32(leftList.Size()) + for _, idx := range rightList.OptionalIndices() { + optIndices = append(optIndices, offset+idx) + } + + combinedList := ctx.NewList(elems, optIndices) + ctx.UpdateExpr(expr, combinedList) + return true + } } return false } -func maybeShortcircuitLogic(ctx *OptimizerContext, function string, args []ast.Expr, expr ast.NavigableExpr) bool { +func maybeShortcircuitLogic(ctx *OptimizerContext, a *ast.AST, function string, args []ast.Expr, expr ast.NavigableExpr) bool { shortcircuit := types.False skip := types.True if function == operators.LogicalOr { @@ -244,10 +291,14 @@ func maybeShortcircuitLogic(ctx *OptimizerContext, function string, args []ast.E } if len(newArgs) == 0 { newArgs = append(newArgs, args[0]) - ctx.UpdateExpr(expr, newArgs[0]) - return true + } + if len(newArgs) == len(args) { + return false } if len(newArgs) == 1 { + if !isBoolType(a, newArgs[0]) { + return false + } ctx.UpdateExpr(expr, newArgs[0]) return true } @@ -255,6 +306,16 @@ func maybeShortcircuitLogic(ctx *OptimizerContext, function string, args []ast.E return true } +func isBoolType(a *ast.AST, e ast.Expr) bool { + if a != nil && a.GetType(e.ID()) == types.BoolType { + return true + } + if e.Kind() == ast.LiteralKind && e.AsLiteral().Type() == types.BoolType { + return true + } + return false +} + // pruneOptionalElements works from the bottom up to resolve optional elements within // aggregate literals. // @@ -285,9 +346,9 @@ func pruneOptionalListElements(ctx *OptimizerContext, e ast.Expr) { updatedElems := []ast.Expr{} updatedIndices := []int32{} newOptIndex := -1 - for _, e := range elems { + for i, e := range elems { newOptIndex++ - if !l.IsOptional(int32(newOptIndex)) { + if !l.IsOptional(int32(i)) { updatedElems = append(updatedElems, e) continue } @@ -501,7 +562,7 @@ func (opt *constantFoldingOptimizer) constantExprMatcher(ctx *OptimizerContext, sel := e.AsSelect() // guaranteed to be a navigable value return constantMatcher(sel.Operand().(ast.NavigableExpr)) case ast.IdentKind: - return opt.knownValues != nil && a.ReferenceMap()[e.ID()] != nil + return opt.knownValues != nil && a.ReferenceMap()[e.ID()] != nil && !hasComprehensionVar(e) case ast.ComprehensionKind: if isNestedComprehension(e) { return false @@ -513,12 +574,15 @@ func (opt *constantFoldingOptimizer) constantExprMatcher(ctx *OptimizerContext, nested := e.AsComprehension() vars[nested.AccuVar()] = true vars[nested.IterVar()] = true + if nested.IterVar2() != "" { + vars[nested.IterVar2()] = true + } } if e.Kind() == ast.IdentKind && !vars[e.AsIdent()] { constantExprs = false } // Late-bound function calls cannot be folded. - if e.Kind() == ast.CallKind && isLateBoundFunctionCall(ctx, a, e) { + if e.Kind() == ast.CallKind && isLateBoundFunctionCall(ctx, e) { constantExprs = false } }) @@ -554,22 +618,43 @@ func constantCallMatcher(e ast.NavigableExpr) bool { return true } } + if fnName == operators.Equals || fnName == operators.NotEquals { + if hasComprehensionVar(e) { + return false + } + if isExprConstantOfKind(children[0], types.BoolType) || isExprConstantOfKind(children[1], types.BoolType) { + return true + } + } if fnName == operators.In { + if hasComprehensionVar(e) { + return false + } haystack := children[1] if haystack.Kind() == ast.ListKind && haystack.AsList().Size() == 0 { return true } needle := children[0] - if needle.Kind() == ast.LiteralKind && haystack.Kind() == ast.ListKind { - needleValue := needle.AsLiteral() + if (needle.Kind() == ast.LiteralKind || isSelfEqualIdent(needle)) && haystack.Kind() == ast.ListKind { + needleIsLit := needle.Kind() == ast.LiteralKind + needleLitVal := needle.AsLiteral() + needleIdentVal := needle.AsIdent() list := haystack.AsList() - for _, e := range list.Elements() { - if e.Kind() == ast.LiteralKind && e.AsLiteral().Equal(needleValue) == types.True { + for _, elem := range list.Elements() { + if needleIsLit && elem.Kind() == ast.LiteralKind && elem.AsLiteral().Equal(needleLitVal) == types.True { + return true + } + if !needleIsLit && elem.Kind() == ast.IdentKind && elem.AsIdent() == needleIdentVal { return true } } } } + if fnName == operators.Add { + if len(children) == 2 && children[0].Kind() == ast.ListKind && children[1].Kind() == ast.ListKind { + return true + } + } // convert all other calls with constant arguments for _, child := range children { if !constantMatcher(child) { @@ -579,6 +664,74 @@ func constantCallMatcher(e ast.NavigableExpr) bool { return true } +// isSelfEqualIdent indicates whether the expression is an identifier whose static type +// guarantees that its runtime value is equal to itself. +// +// Matching an identifier against a list element by name only proves list membership when the +// value the name resolves to is self-equal. A double may be NaN, which is not equal to itself, +// and dyn, abstract, and struct types may all hold a NaN at runtime, so the check is limited +// to the scalar types which cannot, and to the aggregate types whose type parameters are +// themselves self-equal. +func isSelfEqualIdent(e ast.Expr) bool { + if e.Kind() != ast.IdentKind { + return false + } + nav, ok := e.(ast.NavigableExpr) + if !ok { + return false + } + return isSelfEqualType(nav.Type()) +} + +// isSelfEqualType indicates whether all runtime values of the given type are equal to themselves. +func isSelfEqualType(t *types.Type) bool { + if t == nil { + return false + } + switch t.Kind() { + case types.BoolKind, types.BytesKind, types.DurationKind, types.IntKind, + types.NullTypeKind, types.StringKind, types.TimestampKind, types.TypeKind, + types.UintKind: + return true + case types.ListKind, types.MapKind: + // Aggregates compare element-wise, so they are self-equal exactly when their type + // parameters are. A list(dyn) or map(string, double) may still contain a NaN. + for _, p := range t.Parameters() { + if !isSelfEqualType(p) { + return false + } + } + return true + default: + return false + } +} + +func isExprConstantOfKind(e ast.Expr, t *types.Type) bool { + return e.Kind() == ast.LiteralKind && e.AsLiteral().Type() == t +} + +func hasComprehensionVar(e ast.NavigableExpr) bool { + idents := ast.MatchDescendants(e, ast.KindMatcher(ast.IdentKind)) + for _, identNode := range idents { + identName := identNode.AsIdent() + curr := identNode + parent, found := curr.Parent() + for found { + if parent.Kind() == ast.ComprehensionKind { + compre := parent.AsComprehension() + if (compre.AccuVar() == identName || compre.IterVar() == identName || compre.IterVar2() == identName) && + curr.ID() != compre.IterRange().ID() && curr.ID() != compre.AccuInit().ID() { + return true + } + } + curr = parent + parent, found = parent.Parent() + } + } + return false +} + func isNestedComprehension(e ast.NavigableExpr) bool { parent, found := e.Parent() for found { diff --git a/vendor/github.com/google/cel-go/cel/inlining.go b/vendor/github.com/google/cel-go/cel/inlining.go index a4530e19e..d9a5e89a5 100644 --- a/vendor/github.com/google/cel-go/cel/inlining.go +++ b/vendor/github.com/google/cel-go/cel/inlining.go @@ -178,9 +178,38 @@ func (opt *inliningOptimizer) rewritePresenceExpr(ctx *OptimizerContext, prev, i )) return } + if zeroValExpr, ok := zeroValueExpr(ctx, inlinedType); ok { + ctx.UpdateExpr(prev, + ctx.NewCall(operators.NotEquals, + inlined, zeroValExpr)) + return + } ctx.ReportErrorAtID(prev.ID(), "unable to inline expression type %v into presence test", inlinedType) } +// zeroValueExpr creates an expression representing the empty or zero value for the given type +// Note: bytes, lists, maps, and strings are supported via the `SizerType` trait. +func zeroValueExpr(ctx *OptimizerContext, t *Type) (ast.Expr, bool) { + // Note: bytes, strings, lists, and maps are covered by the "sizer-type" check + switch t.Kind() { + case types.BoolKind: + return ctx.NewLiteral(types.False), true + case types.DoubleKind: + return ctx.NewLiteral(types.Double(0)), true + case types.DurationKind: + return ctx.NewCall(overloads.TypeConvertDuration, ctx.NewLiteral(types.String("0s"))), true + case types.IntKind: + return ctx.NewLiteral(types.IntZero), true + case types.TimestampKind: + return ctx.NewCall(overloads.TypeConvertTimestamp, ctx.NewLiteral(types.Int(0))), true + case types.StructKind: + return ctx.NewStruct(t.TypeName(), []ast.EntryExpr{}), true + case types.UintKind: + return ctx.NewLiteral(types.Uint(0)), true + } + return nil, false +} + // isBindable indicates whether the inlined type can be used within a cel.bind() if the expression // being replaced occurs within a presence test. Value types with a size() method or field selection // support can be bound. @@ -212,17 +241,43 @@ func isBindable(matches []ast.NavigableExpr, inlined ast.Expr, inlinedType *Type // field selection. This may be a future refinement. func (opt *inliningOptimizer) matchVariable(varName string) ast.ExprMatcher { return func(e ast.NavigableExpr) bool { - if e.Kind() == ast.IdentKind && e.AsIdent() == varName { - return true + name, found := maybeAsVariableName(e) + if !found || name != varName { + return false + } + + // Determine whether the variable being referenced has been shadowed by a comprehension + p, hasParent := e.Parent() + for hasParent { + if p.Kind() != ast.ComprehensionKind { + p, hasParent = p.Parent() + continue + } + // If the inline variable name matches any of the comprehension variables at any scope, + // return false as the variable has been shadowed. + compre := p.AsComprehension() + if varName == compre.AccuVar() || varName == compre.IterVar() || varName == compre.IterVar2() { + return false + } + p, hasParent = p.Parent() } - if e.Kind() == ast.SelectKind { - sel := e.AsSelect() - // While the `ToQualifiedName` call could take the select directly, this - // would skip presence tests from possible matches, which we would like - // to include. - qualName, found := containers.ToQualifiedName(sel.Operand()) - return found && qualName+"."+sel.FieldName() == varName + + return true + } +} + +func maybeAsVariableName(e ast.NavigableExpr) (string, bool) { + if e.Kind() == ast.IdentKind { + return e.AsIdent(), true + } + if e.Kind() == ast.SelectKind { + sel := e.AsSelect() + // While the `ToQualifiedName` call could take the select directly, this + // would skip presence tests from possible matches, which we would like + // to include. + if qualName, found := containers.ToQualifiedName(sel.Operand()); found { + return qualName + "." + sel.FieldName(), true } - return false } + return "", false } diff --git a/vendor/github.com/google/cel-go/cel/io.go b/vendor/github.com/google/cel-go/cel/io.go index 2e611228d..c991c95c3 100644 --- a/vendor/github.com/google/cel-go/cel/io.go +++ b/vendor/github.com/google/cel-go/cel/io.go @@ -52,7 +52,12 @@ func CheckedExprToAstWithSource(checkedExpr *exprpb.CheckedExpr, src Source) (*A if err != nil { return nil, err } - return &Ast{source: src, impl: checked}, nil + out := &Ast{source: src, impl: checked} + if err := checkLoadedASTDepth(checked); err != nil { + out.loadErr = err + return out, err + } + return out, nil } // AstToCheckedExpr converts an Ast to an protobuf CheckedExpr value. @@ -83,7 +88,26 @@ func ParsedExprToAstWithSource(parsedExpr *exprpb.ParsedExpr, src Source) *Ast { src = common.NewInfoSource(parsedExpr.GetSourceInfo()) } e, _ := ast.ProtoToExpr(parsedExpr.GetExpr()) - return &Ast{source: src, impl: ast.NewAST(e, info)} + out := &Ast{source: src, impl: ast.NewAST(e, info)} + // ParsedExprToAstWithSource has no error return, so record an over-depth violation on the Ast + // to be surfaced when it is later checked or planned. + out.loadErr = checkLoadedASTDepth(out.impl) + return out +} + +// checkLoadedASTDepth guards ASTs that enter through the proto conversion helpers +// (ParsedExprToAst / CheckedExprToAst) against nesting deeper than the parser's recursion limit. +// Those entry points bypass the parser, so without this check a deeply nested loaded AST could +// exhaust the Go stack during later checking or planning. It returns a normal error rather than +// risking that overflow; the traversal itself is bounded so it stays safe on the same input. +// +// Embedders that fully control their AST inputs can skip this by building the AST through the +// common/ast package directly instead of these conversion helpers. +func checkLoadedASTDepth(a *ast.AST) error { + if ast.ExceedsDepth(a, defaultMaxASTDepth) { + return fmt.Errorf("input exceeds maximum expression nesting depth: %d", defaultMaxASTDepth) + } + return nil } // AstToParsedExpr converts an Ast to an protobuf ParsedExpr value. diff --git a/vendor/github.com/google/cel-go/cel/library.go b/vendor/github.com/google/cel-go/cel/library.go index 59a10e81d..43912eaae 100644 --- a/vendor/github.com/google/cel-go/cel/library.go +++ b/vendor/github.com/google/cel-go/cel/library.go @@ -43,6 +43,7 @@ const ( optionalUnwrapFunc = "optional.unwrap" valueFunc = "value" unusedIterVar = "#unused" + targetVar = "@target" ) // Library provides a collection of EnvOption and ProgramOption values used to configure a CEL @@ -97,6 +98,7 @@ func Lib(l Library) EnvOption { if e.HasLibrary(singleton.LibraryName()) { return e, nil } + e.ensureMutableLibraries() e.libraries[singleton.LibraryName()] = singleton } var err error @@ -182,7 +184,9 @@ func (lib *stdLibrary) CompileOptions() []EnvOption { if err = lib.subset.Validate(); err != nil { return nil, err } - e.variables = append(e.variables, stdlib.Types()...) + if len(funcs) > 0 { + e.ensureMutableFunctions() + } for _, fn := range funcs { existing, found := e.functions[fn.Name()] if found { @@ -591,7 +595,7 @@ func (lib *optionalLib) CompileOptions() []EnvOption { // ProgramOptions implements the Library interface method. func (lib *optionalLib) ProgramOptions() []ProgramOption { return []ProgramOption{ - CustomDecorator(decorateOptionalOr), + CustomDecoratorV2(decorateOptionalOr), } } @@ -610,22 +614,38 @@ func optMap(meh MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, * return nil, meh.NewError(varIdent.ID(), "optMap() variable name must be a simple identifier") } mapExpr := args[1] - return meh.NewCall( + targetIdent := target + if target.Kind() != ast.IdentKind { + targetIdent = meh.NewIdent(targetVar) + } + res := meh.NewCall( operators.Conditional, - meh.NewMemberCall(hasValueFunc, target), + meh.NewMemberCall(hasValueFunc, targetIdent), meh.NewCall(optionalOfFunc, meh.NewComprehension( meh.NewList(), unusedIterVar, varName, - meh.NewMemberCall(valueFunc, meh.Copy(target)), + meh.NewMemberCall(valueFunc, meh.Copy(targetIdent)), meh.NewLiteral(types.False), meh.NewIdent(varName), mapExpr, ), ), meh.NewCall(optionalNoneFunc), - ), nil + ) + if target.Kind() != ast.IdentKind { + return meh.NewComprehension( + meh.NewList(), + unusedIterVar, + targetVar, + target, + meh.NewLiteral(types.False), + meh.NewIdent(targetVar), + res, + ), nil + } + return res, nil } func optFlatMap(meh MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *Error) { @@ -638,20 +658,36 @@ func optFlatMap(meh MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Exp return nil, meh.NewError(varIdent.ID(), "optFlatMap() variable name must be a simple identifier") } mapExpr := args[1] - return meh.NewCall( + targetIdent := target + if target.Kind() != ast.IdentKind { + targetIdent = meh.NewIdent(targetVar) + } + res := meh.NewCall( operators.Conditional, - meh.NewMemberCall(hasValueFunc, target), + meh.NewMemberCall(hasValueFunc, targetIdent), meh.NewComprehension( meh.NewList(), unusedIterVar, varName, - meh.NewMemberCall(valueFunc, meh.Copy(target)), + meh.NewMemberCall(valueFunc, meh.Copy(targetIdent)), meh.NewLiteral(types.False), meh.NewIdent(varName), mapExpr, ), meh.NewCall(optionalNoneFunc), - ), nil + ) + if target.Kind() != ast.IdentKind { + return meh.NewComprehension( + meh.NewList(), + unusedIterVar, + targetVar, + target, + meh.NewLiteral(types.False), + meh.NewIdent(targetVar), + res, + ), nil + } + return res, nil } func optUnwrap(value ref.Val) ref.Val { @@ -684,7 +720,7 @@ func EnableErrorOnBadPresenceTest(value bool) EnvOption { return features(featureEnableErrorOnBadPresenceTest, value) } -func decorateOptionalOr(i interpreter.Interpretable) (interpreter.Interpretable, error) { +func decorateOptionalOr(i interpreter.InterpretableV2) (interpreter.InterpretableV2, error) { call, ok := i.(interpreter.InterpretableCall) if !ok { return i, nil @@ -721,8 +757,8 @@ func decorateOptionalOr(i interpreter.Interpretable) (interpreter.Interpretable, // the second optional expression is evaluated and returned. type evalOptionalOr struct { id int64 - lhs interpreter.Interpretable - rhs interpreter.Interpretable + lhs interpreter.InterpretableV2 + rhs interpreter.InterpretableV2 } // ID implements the Interpretable interface method. @@ -730,27 +766,34 @@ func (opt *evalOptionalOr) ID() int64 { return opt.id } -// Eval evaluates the left-hand side optional to determine whether it contains a value, else -// proceeds with the right-hand side evaluation. -func (opt *evalOptionalOr) Eval(ctx interpreter.Activation) ref.Val { +func (opt *evalOptionalOr) Exec(frame *interpreter.ExecutionFrame) ref.Val { // short-circuit lhs. - optLHS := opt.lhs.Eval(ctx) - optVal, ok := optLHS.(*types.Optional) - if !ok { + optLHS := opt.lhs.Exec(frame) + switch val := optLHS.(type) { + case *types.Err, *types.Unknown: return optLHS + case *types.Optional: + if val.HasValue() { + return optLHS + } + return opt.rhs.Exec(frame) + default: + return types.NoSuchOverloadErr() } - if optVal.HasValue() { - return optVal - } - return opt.rhs.Eval(ctx) +} + +// Eval evaluates the left-hand side optional to determine whether it contains a value, else +// proceeds with the right-hand side evaluation. +func (opt *evalOptionalOr) Eval(ctx interpreter.Activation) ref.Val { + return opt.Exec(interpreter.AsFrame(ctx)) } // evalOptionalOrValue selects between an optional or a concrete value. If the optional has a value, // its value is returned, otherwise the alternative value expression is evaluated and returned. type evalOptionalOrValue struct { id int64 - lhs interpreter.Interpretable - rhs interpreter.Interpretable + lhs interpreter.InterpretableV2 + rhs interpreter.InterpretableV2 } // ID implements the Interpretable interface method. @@ -758,19 +801,27 @@ func (opt *evalOptionalOrValue) ID() int64 { return opt.id } -// Eval evaluates the left-hand side optional to determine whether it contains a value, else -// proceeds with the right-hand side evaluation. -func (opt *evalOptionalOrValue) Eval(ctx interpreter.Activation) ref.Val { +func (opt *evalOptionalOrValue) Exec(frame *interpreter.ExecutionFrame) ref.Val { // short-circuit lhs. - optLHS := opt.lhs.Eval(ctx) - optVal, ok := optLHS.(*types.Optional) - if !ok { + optLHS := opt.lhs.Exec(frame) + + switch val := optLHS.(type) { + case *types.Err, *types.Unknown: return optLHS + case *types.Optional: + if val.HasValue() { + return val.GetValue() + } + return opt.rhs.Exec(frame) + default: + return types.NoSuchOverloadErr() } - if optVal.HasValue() { - return optVal.GetValue() - } - return opt.rhs.Eval(ctx) +} + +// Eval evaluates the left-hand side optional to determine whether it contains a value, else +// proceeds with the right-hand side evaluation. +func (opt *evalOptionalOrValue) Eval(ctx interpreter.Activation) ref.Val { + return opt.Exec(interpreter.AsFrame(ctx)) } type timeLegacyLibrary struct{} diff --git a/vendor/github.com/google/cel-go/cel/optimizer.go b/vendor/github.com/google/cel-go/cel/optimizer.go index 9a2a97a64..6e260a93c 100644 --- a/vendor/github.com/google/cel-go/cel/optimizer.go +++ b/vendor/github.com/google/cel-go/cel/optimizer.go @@ -15,6 +15,7 @@ package cel import ( + "fmt" "sort" "github.com/google/cel-go/common" @@ -29,17 +30,43 @@ import ( // passes to ensure that the final optimized output is a valid expression with metadata consistent // with what would have been generated from a parsed and checked expression. // -// Note: source position information is best-effort and likely wrong, but optimized expressions +// Note: source position information is best-effort and incomplete, but optimized expressions // should be suitable for calls to parser.Unparse. type StaticOptimizer struct { optimizers []ASTOptimizer + // If set, Optimize() will use this Source instead of the one from the AST. + sourceOverride *Source } +type OptimizerOption func(*StaticOptimizer) (*StaticOptimizer, error) + // NewStaticOptimizer creates a StaticOptimizer with a sequence of ASTOptimizer's to be applied // to a checked expression. -func NewStaticOptimizer(optimizers ...ASTOptimizer) *StaticOptimizer { - return &StaticOptimizer{ - optimizers: optimizers, +func NewStaticOptimizer(options ...any) (*StaticOptimizer, error) { + so := &StaticOptimizer{} + var err error + for _, opt := range options { + switch v := opt.(type) { + case ASTOptimizer: + so.optimizers = append(so.optimizers, v) + case OptimizerOption: + so, err = v(so) + if err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("unsupported option: %v", v) + } + } + return so, nil +} + +// OptimizeWithSource overrides the source used by the optimizer. +// Note this will cause the source info from the AST passed to Optimize() to be discarded. +func OptimizeWithSource(source Source) OptimizerOption { + return func(so *StaticOptimizer) (*StaticOptimizer, error) { + so.sourceOverride = &source + return so, nil } } @@ -49,15 +76,21 @@ func NewStaticOptimizer(optimizers ...ASTOptimizer) *StaticOptimizer { func (opt *StaticOptimizer) Optimize(env *Env, a *Ast) (*Ast, *Issues) { // Make a copy of the AST to be optimized. optimized := ast.Copy(a.NativeRep()) + source := a.Source() + sourceInfo := optimized.SourceInfo() + if opt.sourceOverride != nil { + source = *opt.sourceOverride + sourceInfo = ast.NewSourceInfo(*opt.sourceOverride) + } ids := newIDGenerator(ast.MaxID(a.NativeRep())) // Create the optimizer context, could be pooled in the future. - issues := NewIssues(common.NewErrors(a.Source())) + issues := NewIssues(common.NewErrors(source)) baseFac := ast.NewExprFactory() exprFac := &optimizerExprFactory{ idGenerator: ids, fac: baseFac, - sourceInfo: optimized.SourceInfo(), + sourceInfo: sourceInfo, } ctx := &OptimizerContext{ optimizerExprFactory: exprFac, @@ -80,7 +113,7 @@ func (opt *StaticOptimizer) Optimize(env *Env, a *Ast) (*Ast, *Issues) { // Recheck the updated expression for any possible type-agreement or validation errors. parsed := &Ast{ - source: a.Source(), + source: source, impl: ast.NewAST(expr, info)} checked, iss := ctx.Check(parsed) if iss.Err() != nil { @@ -91,7 +124,7 @@ func (opt *StaticOptimizer) Optimize(env *Env, a *Ast) (*Ast, *Issues) { // Return the optimized result. return &Ast{ - source: a.Source(), + source: source, impl: optimized, }, nil } @@ -100,6 +133,8 @@ func (opt *StaticOptimizer) Optimize(env *Env, a *Ast) (*Ast, *Issues) { // that the ids within the expression correspond to the ids within macros. func normalizeIDs(idGen ast.IDGenerator, optimized ast.Expr, info *ast.SourceInfo) { optimized.RenumberIDs(idGen) + info.RenumberIDs(idGen) + if len(info.MacroCalls()) == 0 { return } @@ -260,6 +295,9 @@ func (opt *optimizerExprFactory) CopyASTAndMetadata(a *ast.AST) ast.Expr { for macroID, call := range copyInfo.MacroCalls() { opt.SetMacroCall(macroID, call) } + for id, offset := range copyInfo.OffsetRanges() { + opt.sourceInfo.SetOffsetRange(id, offset) + } return copyExpr } diff --git a/vendor/github.com/google/cel-go/cel/options.go b/vendor/github.com/google/cel-go/cel/options.go index fee67323c..709ce007a 100644 --- a/vendor/github.com/google/cel-go/cel/options.go +++ b/vendor/github.com/google/cel-go/cel/options.go @@ -24,6 +24,7 @@ import ( "google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/types/dynamicpb" + "github.com/google/cel-go/cel/async" "github.com/google/cel-go/checker" "github.com/google/cel-go/common/containers" "github.com/google/cel-go/common/decls" @@ -71,12 +72,16 @@ const ( // Enable escape syntax for field identifiers (`). featureIdentEscapeSyntax + + // Enable accessing fields by JSON names within protobuf messages + featureJSONFieldNames ) var featureIDsToNames = map[int]string{ featureEnableMacroCallTracking: "cel.feature.macro_call_tracking", featureCrossTypeNumericComparisons: "cel.feature.cross_type_numeric_comparisons", featureIdentEscapeSyntax: "cel.feature.backtick_escape_syntax", + featureJSONFieldNames: "cel.feature.json_field_names", } func featureNameByID(id int) (string, bool) { @@ -93,6 +98,54 @@ func featureIDByName(name string) (int, bool) { return 0, false } +// limitID is used as a key for configurable limits. These are options that +// support exporting to YAML environment config. +type limitID int + +const ( + _ = limitID(iota) + // The number of recursive calls permitted in parsing. + limitParseRecursionDepth + // The number of code points permitted in an input expression string. + limitCodePointSize + // The number of attempts to recover from a parse error. + limitParseErrorRecovery + // The maximum nesting depth permitted for ASTs loaded outside the parser. + limitMaxASTDepth + // The maximum number of expression nodes permitted in parsing (including macro expansion). + limitExpressionNodeCount + // The maximum regex program plan size permitted. + limitRegexProgramSize +) + +// defaultMaxASTDepth mirrors the parser's default maxRecursionDepth (250) and +// is applied to ASTs that enter through non-parser ingestion paths (e.g. via +// ParsedExprToAst / CheckedExprToAst) when no explicit limit is configured. +const defaultMaxASTDepth = 250 + +var limitIDsToNames = map[limitID]string{ + limitCodePointSize: "cel.limit.expression_code_points", + limitParseErrorRecovery: "cel.limit.parse_error_recovery", + limitParseRecursionDepth: "cel.limit.parse_recursion_depth", + limitMaxASTDepth: "cel.limit.max_ast_depth", + limitExpressionNodeCount: "cel.limit.expression_node_count", + limitRegexProgramSize: "cel.limit.regex_program_size", +} + +func limitNameByID(id limitID) (string, bool) { + v, ok := limitIDsToNames[id] + return v, ok +} + +func limitIDByName(name string) (limitID, bool) { + for k, v := range limitIDsToNames { + if v == name { + return k, true + } + } + return limitID(0), false +} + // EnvOption is a functional interface for configuring the environment. type EnvOption func(e *Env) (*Env, error) @@ -275,9 +328,9 @@ func Abbrevs(qualifiedNames ...string) EnvOption { } } -// customTypeRegistry is an internal-only interface containing the minimum methods required to support +// protoTypeRegistry is an internal-only interface containing the minimum methods required to support // custom types. It is a subset of methods from ref.TypeRegistry. -type customTypeRegistry interface { +type protoTypeRegistry interface { RegisterDescriptor(protoreflect.FileDescriptor) error RegisterType(...ref.Type) error } @@ -294,7 +347,7 @@ type customTypeRegistry interface { // Note: This option must be specified after the CustomTypeProvider option when used together. func Types(addTypes ...any) EnvOption { return func(e *Env) (*Env, error) { - reg, isReg := e.provider.(customTypeRegistry) + reg, isReg := e.provider.(protoTypeRegistry) if !isReg { return nil, fmt.Errorf("custom types not supported by provider: %T", e.provider) } @@ -331,7 +384,7 @@ func Types(addTypes ...any) EnvOption { // extension or by re-using the same EnvOption with another NewEnv() call. func TypeDescs(descs ...any) EnvOption { return func(e *Env) (*Env, error) { - reg, isReg := e.provider.(customTypeRegistry) + reg, isReg := e.provider.(protoTypeRegistry) if !isReg { return nil, fmt.Errorf("custom types not supported by provider: %T", e.provider) } @@ -379,7 +432,7 @@ func TypeDescs(descs ...any) EnvOption { } } -func registerFileSet(reg customTypeRegistry, fileSet *descpb.FileDescriptorSet) error { +func registerFileSet(reg protoTypeRegistry, fileSet *descpb.FileDescriptorSet) error { files, err := protodesc.NewFiles(fileSet) if err != nil { return fmt.Errorf("protodesc.NewFiles(%v) failed: %v", fileSet, err) @@ -387,7 +440,7 @@ func registerFileSet(reg customTypeRegistry, fileSet *descpb.FileDescriptorSet) return registerFiles(reg, files) } -func registerFiles(reg customTypeRegistry, files *protoregistry.Files) error { +func registerFiles(reg protoTypeRegistry, files *protoregistry.Files) error { var err error files.RangeFiles(func(fd protoreflect.FileDescriptor) bool { err = reg.RegisterDescriptor(fd) @@ -396,6 +449,15 @@ func registerFiles(reg customTypeRegistry, files *protoregistry.Files) error { return err } +// JSONFieldNames supports accessing protocol buffer fields by json-name. +// +// Enabling JSON field name support will create a copy of the types.Registry with fields indexed +// by JSON name, and whether JSON name or Proto-style names are supported will be inferred from +// the AST extensions metadata. +func JSONFieldNames(enabled bool) EnvOption { + return features(featureJSONFieldNames, enabled) +} + // ProgramOption is a functional interface for configuring evaluation bindings and behaviors. type ProgramOption func(p *prog) (*prog, error) @@ -409,6 +471,14 @@ func CustomDecorator(dec interpreter.InterpretableDecorator) ProgramOption { } } +// CustomDecoratorV2 appends an InterpreterDecoratorV2 to the program. +func CustomDecoratorV2(dec interpreter.InterpretableDecoratorV2) ProgramOption { + return func(p *prog) (*prog, error) { + p.plannerOptions = append(p.plannerOptions, interpreter.CustomDecoratorV2(dec)) + return p, nil + } +} + // Functions adds function overloads that extend or override the set of CEL built-ins. // // Deprecated: use Function() instead to declare the function, its overload signatures, @@ -523,6 +593,17 @@ func configToEnvOptions(config *env.Config, provider types.Provider, optFactorie envOpts = append(envOpts, Abbrevs(imp.Name)) } + // Configure features and common limits. + for _, feat := range config.Features { + // Note, if a feature is not found, it is skipped as it is possible the feature + // is not intended to be supported publicly. In the future, a refinement of + // to this strategy to report unrecognized features and validators should probably + // be covered as a standard ConfigOptionFactory + if id, found := featureIDByName(feat.Name); found { + envOpts = append(envOpts, features(id, feat.Enabled)) + } + } + // Configure the context variable declaration if config.ContextVariable != nil { typeName := config.ContextVariable.TypeName @@ -564,14 +645,9 @@ func configToEnvOptions(config *env.Config, provider types.Provider, optFactorie envOpts = append(envOpts, FunctionDecls(funcs...)) } - // Configure features - for _, feat := range config.Features { - // Note, if a feature is not found, it is skipped as it is possible the feature - // is not intended to be supported publicly. In the future, a refinement of - // to this strategy to report unrecognized features and validators should probably - // be covered as a standard ConfigOptionFactory - if id, found := featureIDByName(feat.Name); found { - envOpts = append(envOpts, features(id, feat.Enabled)) + for _, limit := range config.Limits { + if id, found := limitIDByName(limit.Name); found { + envOpts = append(envOpts, setLimit(id, limit.Value)) } } @@ -674,6 +750,47 @@ func InterruptCheckFrequency(checkFrequency uint) ProgramOption { } } +// AsyncCallObserver sets the observer for monitoring asynchronous function calls during ConcurrentEval. +func AsyncCallObserver(observer async.Observer) ProgramOption { + return func(p *prog) (*prog, error) { + p.asyncObserver = observer + return p, nil + } +} + +// AsyncCompletionBufferSize sets the size of the buffer for the async completion channel. +// By default, the channel is unbuffered. +func AsyncCompletionBufferSize(size int) ProgramOption { + return func(p *prog) (*prog, error) { + p.asyncCompletionBufferSize = size + return p, nil + } +} + +// AsyncMaxConcurrency sets the maximum number of concurrently launched async calls during +// ConcurrentEval. This bounds the number of in-flight async goroutines, so a wide fan-out (such +// as an async call inside a comprehension over a large list) cannot exhaust memory. +// +// A value of 0 (unset) applies a built-in default bound. A positive value sets an explicit bound. +// A negative value disables the limiter (unbounded launches) and should only be used when +// concurrency is bounded by other means. +func AsyncMaxConcurrency(maxConcurrency int) ProgramOption { + return func(p *prog) (*prog, error) { + p.asyncMaxConcurrency = maxConcurrency + return p, nil + } +} + +// ConcurrentDrainStrategy configures the strategy for when to re-evaluate the program +// during a ConcurrentEval call after receiving asynchronous completion signals. +// By default, the program re-evaluates immediately after every completion. +func ConcurrentDrainStrategy(strategy async.DrainStrategy) ProgramOption { + return func(p *prog) (*prog, error) { + p.drainStrategy = strategy + return p, nil + } +} + // CostEstimatorOptions configure type-check time options for estimating expression cost. func CostEstimatorOptions(costOpts ...checker.CostOption) EnvOption { return func(e *Env) (*Env, error) { @@ -727,8 +844,11 @@ func fieldToCELType(field protoreflect.FieldDescriptor) (*Type, error) { return nil, fmt.Errorf("field %s type %s not implemented", field.FullName(), field.Kind().String()) } -func fieldToVariable(field protoreflect.FieldDescriptor) (*decls.VariableDecl, error) { +func fieldToVariable(field protoreflect.FieldDescriptor, jsonFieldNames bool) (*decls.VariableDecl, error) { name := string(field.Name()) + if jsonFieldNames { + name = field.JSONName() + } if field.IsMap() { mapKey := field.MapKey() mapValue := field.MapValue() @@ -759,6 +879,8 @@ func fieldToVariable(field protoreflect.FieldDescriptor) (*decls.VariableDecl, e // DeclareContextProto returns an option to extend CEL environment with declarations from the given context proto. // Each field of the proto defines a variable of the same name in the environment. // https://github.com/google/cel-spec/blob/master/doc/langdef.md#evaluation-environment +// +// If using JSONFieldNames(), ensure that the option is set before DeclareContextProto is provided. func DeclareContextProto(descriptor protoreflect.MessageDescriptor) EnvOption { return func(e *Env) (*Env, error) { if e.contextProto != nil { @@ -768,9 +890,10 @@ func DeclareContextProto(descriptor protoreflect.MessageDescriptor) EnvOption { e.contextProto = descriptor fields := descriptor.Fields() vars := make([]*decls.VariableDecl, 0, fields.Len()) + jsonFieldNames := e.HasFeature(featureJSONFieldNames) for i := 0; i < fields.Len(); i++ { field := fields.Get(i) - variable, err := fieldToVariable(field) + variable, err := fieldToVariable(field, jsonFieldNames) if err != nil { return nil, err } @@ -789,11 +912,19 @@ func DeclareContextProto(descriptor protoreflect.MessageDescriptor) EnvOption { // // Consider using with `DeclareContextProto` to simplify variable type declarations and publishing when using // protocol buffers. -func ContextProtoVars(ctx proto.Message) (Activation, error) { +// +// Use the types.JSONFieldNames(true) option to populate the context proto vars using the JSON field names. +func ContextProtoVars(ctx proto.Message, opts ...types.RegistryOption) (Activation, error) { if ctx == nil || !ctx.ProtoReflect().IsValid() { return interpreter.EmptyActivation(), nil } - reg, err := types.NewRegistry(ctx) + regOpts := []types.RegistryOption{types.ProtoTypeDefs(ctx)} + regOpts = append(regOpts, opts...) + var ro []any + for _, opt := range regOpts { + ro = append(ro, opt) + } + reg, err := types.NewRegistry(ro...) if err != nil { return nil, err } @@ -803,15 +934,19 @@ func ContextProtoVars(ctx proto.Message) (Activation, error) { vars := make(map[string]any, fields.Len()) for i := 0; i < fields.Len(); i++ { field := fields.Get(i) - sft, found := reg.FindStructFieldType(typeName, field.TextName()) + fieldName := field.TextName() + if reg.JSONFieldNames() { + fieldName = field.JSONName() + } + sft, found := reg.FindStructFieldType(typeName, fieldName) if !found { - return nil, fmt.Errorf("no such field: %s", field.TextName()) + return nil, fmt.Errorf("no such field: %s", fieldName) } fieldVal, err := sft.GetFrom(ctx) if err != nil { return nil, err } - vars[field.TextName()] = fieldVal + vars[fieldName] = fieldVal } return NewActivation(vars) } @@ -842,25 +977,75 @@ func DefaultUTCTimeZone(enabled bool) EnvOption { // features sets the given feature flags. See list of Feature constants above. func features(flag int, enabled bool) EnvOption { return func(e *Env) (*Env, error) { + e.ensureMutableFeatures() e.features[flag] = enabled return e, nil } } -// ParserRecursionLimit adjusts the AST depth the parser will tolerate. -// Defaults defined in the parser package. -func ParserRecursionLimit(limit int) EnvOption { +func setLimit(id limitID, limit int) EnvOption { + if limit < 0 { + limit = -1 + } return func(e *Env) (*Env, error) { - e.prsrOpts = append(e.prsrOpts, parser.MaxRecursionDepth(limit)) + e.ensureMutableLimits() + e.limits[id] = limit return e, nil } } -// ParserExpressionSizeLimit adjusts the number of code points the expression parser is allowed to parse. +// ParserRecursionLimit adjusts the AST depth the parser will tolerate. // Defaults defined in the parser package. +func ParserRecursionLimit(limit int) EnvOption { + return setLimit(limitParseRecursionDepth, limit) +} + +// ParserErrorRecoveryLimit sets the number of attemtps the parser will take +// to recover after encountering an error. +func ParserErrorRecoveryLimit(limit int) EnvOption { + return setLimit(limitParseErrorRecovery, limit) +} + +// ParserExpressionSizeLimit adjusts the number of code points the expression parser is allowed to parse. +// Defaults are defined in the parser package. A negative value means unbounded. func ParserExpressionSizeLimit(limit int) EnvOption { + return setLimit(limitCodePointSize, limit) +} + +// ExpressionNodeLimit adjusts the maximum number of expression nodes permitted during parsing +// and checking, including nodes created by macro expansion. Defaults are defined in the parser +// package (100,000). A negative value means unbounded. +func ExpressionNodeLimit(limit int) EnvOption { + return setLimit(limitExpressionNodeCount, limit) +} + +// ExpressionNestingDepthLimit records the maximum nesting depth permitted for ASTs in the +// environment configuration so that the value round-trips through env.Config export/import. +// +// ASTs loaded outside the parser (e.g. via ParsedExprToAst / CheckedExprToAst) bypass the +// parser's recursion limit, so those conversion paths validate nesting depth against the +// parser-matching default (250) to avoid a Go stack overflow during later checking or planning. +// Embedders that fully control their AST inputs and want to skip the check can construct the AST +// through the common/ast package directly rather than the cel conversion helpers. +func ExpressionNestingDepthLimit(limit int) EnvOption { + return setLimit(limitMaxASTDepth, limit) +} + +// RegexProgramSizeLimit caps the maximum regex program plan size permitted for regular expressions. +// A negative or zero value means unbounded. +func RegexProgramSizeLimit(limit int) EnvOption { return func(e *Env) (*Env, error) { - e.prsrOpts = append(e.prsrOpts, parser.ExpressionSizeCodePointLimit(limit)) + var err error + e, err = setLimit(limitRegexProgramSize, limit)(e) + if err != nil { + return nil, err + } + if limit > 0 { + e, err = ASTValidators(ValidateRegexProgramSizeLimit(limit))(e) + if err != nil { + return nil, err + } + } return e, nil } } diff --git a/vendor/github.com/google/cel-go/cel/program.go b/vendor/github.com/google/cel-go/cel/program.go index 24f41a4a7..07849974a 100644 --- a/vendor/github.com/google/cel-go/cel/program.go +++ b/vendor/github.com/google/cel-go/cel/program.go @@ -16,10 +16,14 @@ package cel import ( "context" + "errors" "fmt" - "sync" + "time" + "github.com/google/cel-go/cel/async" "github.com/google/cel-go/common/ast" + "github.com/google/cel-go/common/operators" + "github.com/google/cel-go/common/overloads" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" "github.com/google/cel-go/interpreter" @@ -51,6 +55,21 @@ type Program interface { // // The output contract for `ContextEval` is otherwise identical to the `Eval` method. ContextEval(context.Context, any) (ref.Val, *EvalDetails, error) + + // ConcurrentEval evaluates the program concurrently, returning a channel that will receive + // the final EvalResult when all asynchronous operations complete, or the context expires. + // + // The vars value may either be an `Activation` or `map[string]any`. + // + // Liveness: ConcurrentEval relies on context cancellation to terminate. If an async function + // never returns and does not honor its context, and the supplied context has no deadline, the + // call will block indefinitely. Always pass a context with a deadline or cancellation. + // + // Error handling is fail-fast: as soon as a re-evaluation pass yields an error, that error is + // returned and any still in-flight async calls are cancelled (their contexts are done) and + // their results discarded. Async functions should therefore be free of unwanted side effects + // on partial evaluation, or guard them with idempotency/cancellation handling. + ConcurrentEval(context.Context, any) <-chan EvalResult } // Activation used to resolve identifiers by name and references by id. @@ -143,6 +162,13 @@ func (ed *EvalDetails) ActualCost() *uint64 { return &cost } +// EvalResult encapsulates the response from a ConcurrentEval call. +type EvalResult struct { + Val ref.Val + EvalDetails *EvalDetails + Err error +} + // prog is the internal implementation of the Program interface. type prog struct { *Env @@ -158,11 +184,45 @@ type prog struct { regexOptimizations []*interpreter.RegexOptimization // Interpretable configured from an Ast and aggregate decorator set based on program options. - interpretable interpreter.Interpretable + interpretable interpreter.InterpretableV2 observable *interpreter.ObservableInterpretable callCostEstimator interpreter.ActualCostEstimator costOptions []interpreter.CostTrackerOption costLimit *uint64 + + // hasAsync indicates the planned expression contains an asynchronous function call, which can + // only be resolved by ConcurrentEval. + hasAsync bool + + // Async evaluation configuration used by ConcurrentEval. + drainStrategy async.DrainStrategy + asyncObserver async.Observer + asyncCompletionBufferSize int + asyncMaxConcurrency int +} + +// scanOptTargets walks the AST once and reports whether the Optimize() +// decorator (needOpt) and the regex-constant compiler (needRegex) have any +// target node present. The conditions are a superset of what each decorator +// acts on, so a false negative — skipping a decorator that would have +// optimized something — is impossible. +func scanOptTargets(root ast.Expr) (needOpt, needRegex bool) { + ast.PostOrderVisit(root, ast.NewExprVisitor(func(e ast.Expr) { + switch e.Kind() { + case ast.ListKind, ast.MapKind: + needOpt = true // maybeBuildListLiteral / maybeBuildMapLiteral + case ast.CallKind: + switch fn := e.AsCall().FunctionName(); { + case fn == overloads.Matches: + needRegex = true + case fn == operators.In || fn == operators.OldIn: + needOpt = true // maybeOptimizeSetMembership + case overloads.IsTypeConversionFunction(fn): + needOpt = true // maybeOptimizeConstUnary + } + } + })) + return } // newProgram creates a program instance with an environment, an ast, and an optional list of @@ -170,8 +230,16 @@ type prog struct { // // If the program cannot be configured the prog will be nil, with a non-nil error response. func newProgram(e *Env, a *ast.AST, opts []ProgramOption) (Program, error) { - // Build the dispatcher, interpreter, and default program value. - disp := interpreter.NewDispatcher() + // Build the env's function bindings and shared dispatcher once (pure functions of the + // env). The dispatcher holding the env's function bindings is identical across every + // Program() built from it and read-only during planning — so assemble it once per env + // and layer a thin child over it here for per-program Functions() isolation, rather than + // re-indexing overloads on every Program() call. + sharedDisp, hasAsync, err := e.initDispatcher() + if err != nil { + return nil, err + } + disp := interpreter.ExtendDispatcher(sharedDisp) // Ensure the default attribute factory is set after the adapter and provider are // configured. @@ -180,10 +248,11 @@ func newProgram(e *Env, a *ast.AST, opts []ProgramOption) (Program, error) { plannerOptions: []interpreter.PlannerOption{}, dispatcher: disp, costOptions: []interpreter.CostTrackerOption{}, + drainStrategy: async.DrainReady(100 * time.Microsecond), + hasAsync: hasAsync, } // Configure the program via the ProgramOption values. - var err error for _, opt := range opts { p, err = opt(p) if err != nil { @@ -191,23 +260,17 @@ func newProgram(e *Env, a *ast.AST, opts []ProgramOption) (Program, error) { } } - // Add the function bindings created via Function() options. - for _, fn := range e.functions { - bindings, err := fn.Bindings() - if err != nil { - return nil, err - } - err = disp.Add(bindings...) - if err != nil { - return nil, err - } - } - // Set the attribute factory after the options have been set. var attrFactory interpreter.AttributeFactory attrFactorOpts := []interpreter.AttrFactoryOption{ interpreter.EnableErrorOnBadPresenceTest(p.HasFeature(featureEnableErrorOnBadPresenceTest)), } + if a.SourceInfo().HasExtension("json_name", ast.NewExtensionVersion(1, 1)) { + if !e.HasFeature(featureJSONFieldNames) { + return nil, errors.New("the AST extension 'json_name' requires the option cel.JSONFieldNames(true)") + } + } + // Configure the type provider, considering whether the AST indicates whether it supports JSON field names if p.evalOpts&OptPartialEval == OptPartialEval { attrFactory = interpreter.NewPartialAttributeFactory(e.Container, e.adapter, e.provider, attrFactorOpts...) } else { @@ -226,13 +289,29 @@ func newProgram(e *Env, a *ast.AST, opts []ProgramOption) (Program, error) { } // Enable constant folding first. if p.evalOpts&OptOptimize == OptOptimize { - plannerOptions = append(plannerOptions, interpreter.Optimize()) - p.regexOptimizations = append(p.regexOptimizations, interpreter.MatchesRegexOptimization) + // The Optimize() decorator (set-membership, constant list/map literals, + // const type conversions) and the regex-constant compiler each walk + // every planned node. When the AST provably contains no node they can + // act on, adding them is pure overhead — so gate each on a single AST + // scan. The scan condition is a superset of what the decorators touch, + // so a decorator is only skipped when its target node is definitely + // absent and evaluation is never affected (an ungated regex would + // recompile per eval, etc.). + addOptimize, addRegex := scanOptTargets(a.Expr()) + if addOptimize { + plannerOptions = append(plannerOptions, interpreter.Optimize()) + } + if addRegex { + p.regexOptimizations = append(p.regexOptimizations, interpreter.MatchesRegexOptimization) + } } // Enable regex compilation of constants immediately after folding constants. if len(p.regexOptimizations) > 0 { plannerOptions = append(plannerOptions, interpreter.CompileRegexConstants(p.regexOptimizations...)) } + if limit := p.limits[limitRegexProgramSize]; limit > 0 { + plannerOptions = append(plannerOptions, interpreter.RegexProgramSizeLimit(limit)) + } // Enable exhaustive eval, state tracking and cost tracking last since they require a factory. if p.evalOpts&(OptExhaustiveEval|OptTrackState|OptTrackCost) != 0 { @@ -245,8 +324,16 @@ func newProgram(e *Env, a *ast.AST, opts []ProgramOption) (Program, error) { if p.costLimit != nil { costOpts = append(costOpts, interpreter.CostTrackerLimit(*p.costLimit)) } + // Creating a new cost tracker for each evaluation causes significant work that + // needs to be repeated for each evaluation even though the cost tracker is + // mostly read-only once constructed. Therefore it gets constructed + // once now and later a cheap clone is used for each evaluation. + tracker, err := interpreter.NewCostTracker(p.callCostEstimator, costOpts...) + if err != nil { + return nil, fmt.Errorf("construct cost tracker: %w", err) + } trackerFactory := func() (*interpreter.CostTracker, error) { - return interpreter.NewCostTracker(p.callCostEstimator, costOpts...) + return tracker.Clone() } var observers []interpreter.PlannerOption if p.evalOpts&(OptExhaustiveEval|OptTrackState) != 0 { @@ -295,23 +382,25 @@ func (p *prog) Eval(input any) (out ref.Val, det *EvalDetails, err error) { } } }() - // Build a hierarchical activation if there are default vars set. - var vars Activation - switch v := input.(type) { - case Activation: - vars = v - case map[string]any: - vars = activationPool.Setup(v) - defer activationPool.Put(vars) - default: - return nil, nil, fmt.Errorf("invalid input, wanted Activation or map[string]any, got: (%T)%v", input, input) + // Asynchronous calls cannot be resolved by a single-pass evaluation. Reject before doing any + // work (this also covers ContextEval, which delegates here); ConcurrentEval does not call Eval. + if p.hasAsync { + return nil, nil, errAsyncRequiresConcurrentEval } - if p.defaultVars != nil { - vars = interpreter.NewHierarchicalActivation(p.defaultVars, vars) + // Build a hierarchical activation if there are default vars set. + var frame *interpreter.ExecutionFrame + if f, ok := input.(*interpreter.ExecutionFrame); ok { + frame = f + } else { + frame, err = p.newExecutionFrame(input) + if err != nil { + return nil, nil, err + } + defer frame.Close() } if p.observable != nil { det = &EvalDetails{} - out = p.observable.ObserveEval(vars, func(observed any) { + out = p.observable.ObserveExec(frame, func(observed any) { switch o := observed.(type) { case interpreter.EvalState: det.state = o @@ -320,7 +409,7 @@ func (p *prog) Eval(input any) (out ref.Val, det *EvalDetails, err error) { } }) } else { - out = p.interpretable.Eval(vars) + out = p.interpretable.Exec(frame) } // The output of an internal Eval may have a value (`v`) that is a types.Err. This step // translates the CEL value to a Go error response. This interface does not quite match the @@ -336,160 +425,220 @@ func (p *prog) ContextEval(ctx context.Context, input any) (ref.Val, *EvalDetail if ctx == nil { return nil, nil, fmt.Errorf("context can not be nil") } - // Configure the input, making sure to wrap Activation inputs in the special ctxActivation which - // exposes the #interrupted variable and manages rate-limited checks of the ctx.Done() state. - var vars Activation - switch v := input.(type) { - case Activation: - vars = ctxActivationPool.Setup(v, ctx.Done(), p.interruptCheckFrequency) - defer ctxActivationPool.Put(vars) - case map[string]any: - rawVars := activationPool.Setup(v) - defer activationPool.Put(rawVars) - vars = ctxActivationPool.Setup(rawVars, ctx.Done(), p.interruptCheckFrequency) - defer ctxActivationPool.Put(vars) - default: - return nil, nil, fmt.Errorf("invalid input, wanted Activation or map[string]any, got: (%T)%v", input, input) + frame, err := p.newExecutionFrame(input) + if err != nil { + return nil, nil, err } - return p.Eval(vars) -} - -type ctxEvalActivation struct { - parent Activation - interrupt <-chan struct{} - interruptCheckCount uint - interruptCheckFrequency uint + defer frame.Close() + frame.SetContext(ctx, p.interruptCheckFrequency) + out, det, errEval := p.Eval(frame) + if errEval != nil && errors.Is(errEval, interpreter.InterruptError{}) { + return out, det, fmt.Errorf("%w: %w", errEval, context.Cause(ctx)) + } + return out, det, errEval } -// ResolveName implements the Activation interface method, but adds a special #interrupted variable -// which is capable of testing whether a 'done' signal is provided from a context.Context channel. -func (a *ctxEvalActivation) ResolveName(name string) (any, bool) { - if name == "#interrupted" { - a.interruptCheckCount++ - if a.interruptCheckCount%a.interruptCheckFrequency == 0 { - select { - case <-a.interrupt: - return true, true - default: - return nil, false - } - } - return nil, false +// newExecutionFrame creates an ExecutionFrame for the given input without a timeout context. +func (p *prog) newExecutionFrame(input any) (*interpreter.ExecutionFrame, error) { + frame, err := interpreter.NewExecutionFrame(input) + if err != nil { + return nil, err + } + if p.defaultVars != nil { + // Update the frame's activation in place. + frame.Activation = interpreter.NewHierarchicalActivation(p.defaultVars, frame.Activation) } - return a.parent.ResolveName(name) -} -func (a *ctxEvalActivation) Parent() Activation { - return a.parent + return frame, nil } -func (a *ctxEvalActivation) AsPartialActivation() (interpreter.PartialActivation, bool) { - pa, ok := a.parent.(interpreter.PartialActivation) - return pa, ok +// newAsyncFrame creates an ExecutionFrame configured for asynchronous evaluation under the +// given context, wiring the observer and concurrency limit from the program options. +func (p *prog) newAsyncFrame(ctx context.Context, input any) (*interpreter.ExecutionFrame, error) { + frame, err := p.newExecutionFrame(input) + if err != nil { + return nil, err + } + if err := frame.SetContext(ctx, p.interruptCheckFrequency); err != nil { + frame.Close() + return nil, err + } + frame.SetAsyncObserver(p.asyncObserver) + frame.SetAsyncMaxConcurrency(resolveAsyncMaxConcurrency(p.asyncMaxConcurrency)) + return frame, nil } -func newCtxEvalActivationPool() *ctxEvalActivationPool { - return &ctxEvalActivationPool{ - Pool: sync.Pool{ - New: func() any { - return &ctxEvalActivation{} - }, - }, +// defaultAsyncMaxConcurrency bounds the number of concurrently launched async calls when the +// program does not configure AsyncMaxConcurrency. It exists so that a wide fan-out (e.g. an async +// call inside a comprehension over a large list) cannot spawn an unbounded number of goroutines. +const defaultAsyncMaxConcurrency = 100 + +// resolveAsyncMaxConcurrency maps the configured concurrency to the effective launch limit: +// - 0 (unset): apply defaultAsyncMaxConcurrency. +// - >0: use the configured value. +// - <0: unlimited (no launch limiter); use only if the caller bounds concurrency another way. +func resolveAsyncMaxConcurrency(configured int) int { + if configured == 0 { + return defaultAsyncMaxConcurrency } + return configured } -type ctxEvalActivationPool struct { - sync.Pool +// resolveCompletionBufferSize returns the size of the async completion channel. When unset, it +// defaults to the effective launch concurrency so that all in-flight calls can report completion +// without blocking. An unbuffered channel would make a completed call hold its launch slot until +// the evaluator drained it, throttling effective concurrency to the drain rate. +func (p *prog) resolveCompletionBufferSize() int { + if p.asyncCompletionBufferSize > 0 { + return p.asyncCompletionBufferSize + } + limit := resolveAsyncMaxConcurrency(p.asyncMaxConcurrency) + if limit < 0 { + // Unlimited launches: fall back to the default bound for the buffer so it stays finite. + return defaultAsyncMaxConcurrency + } + return limit } -// Setup initializes a pooled Activation with the ability check for context.Context cancellation -func (p *ctxEvalActivationPool) Setup(vars Activation, done <-chan struct{}, interruptCheckRate uint) *ctxEvalActivation { - a := p.Pool.Get().(*ctxEvalActivation) - a.parent = vars - a.interrupt = done - a.interruptCheckCount = 0 - a.interruptCheckFrequency = interruptCheckRate - return a -} +// ConcurrentEval implements the Program interface. +func (p *prog) ConcurrentEval(ctx context.Context, input any) <-chan EvalResult { + resCh := make(chan EvalResult, 1) + if ctx == nil { + resCh <- EvalResult{Err: errors.New("context can not be nil")} + close(resCh) + return resCh + } -type evalActivation struct { - vars map[string]any - lazyVars map[string]any -} + go func() { + defer close(resCh) + // Ensure concurrent eval handles panic / recovery properly + defer func() { + if r := recover(); r != nil { + switch t := r.(type) { + case interpreter.EvalCancelledError: + resCh <- EvalResult{Err: t} + default: + resCh <- EvalResult{Err: fmt.Errorf("internal error: %v", r)} + } + } + }() -// ResolveName looks up the value of the input variable name, if found. -// -// Lazy bindings may be supplied within the map-based input in either of the following forms: -// - func() any -// - func() ref.Val -// -// The lazy binding will only be invoked once per evaluation. -// -// Values which are not represented as ref.Val types on input may be adapted to a ref.Val using -// the types.Adapter configured in the environment. -func (a *evalActivation) ResolveName(name string) (any, bool) { - v, found := a.vars[name] - if !found { - return nil, false - } - switch obj := v.(type) { - case func() ref.Val: - if resolved, found := a.lazyVars[name]; found { - return resolved, true - } - lazy := obj() - a.lazyVars[name] = lazy - return lazy, true - case func() any: - if resolved, found := a.lazyVars[name]; found { - return resolved, true + frame, err := p.newAsyncFrame(ctx, input) + if err != nil { + resCh <- EvalResult{Err: err} + return } - lazy := obj() - a.lazyVars[name] = lazy - return lazy, true - default: - return obj, true - } -} + defer frame.Close() + + // Completions are signaled to this channel as async calls finish. The asyncCallState + // fan-in also selects on ctx.Done(), so the sender will not leak if this loop returns early. + completions := make(chan int64, p.resolveCompletionBufferSize()) + frame.SetCompletions(completions) + + for { + var out ref.Val + var det *EvalDetails + + if p.observable != nil { + det = &EvalDetails{} + out = p.observable.ObserveExec(frame, func(observed any) { + switch o := observed.(type) { + case interpreter.EvalState: + det.state = o + case *interpreter.CostTracker: + det.costTracker = o + } + }) + } else { + out = p.interpretable.Exec(frame) + } -// Parent implements the Activation interface -func (a *evalActivation) Parent() Activation { - return nil -} + // Communicate errors quickly. + if types.IsError(out) { + var err error = out.(*types.Err) + if errors.Is(err, interpreter.InterruptError{}) { + err = fmt.Errorf("%w: %w", err, context.Cause(ctx)) + } + resCh <- EvalResult{Val: out, EvalDetails: det, Err: err} + return + } -func newEvalActivationPool() *evalActivationPool { - return &evalActivationPool{ - Pool: sync.Pool{ - New: func() any { - return &evalActivation{lazyVars: make(map[string]any)} - }, - }, - } -} + // A concrete (non-unknown) result is final. + unk, isUnknown := out.(*types.Unknown) + if !isUnknown || !unk.HasUnknownFunction() { + resCh <- EvalResult{Val: out, EvalDetails: det, Err: nil} + return + } -type evalActivationPool struct { - sync.Pool -} + // Post-execution dispatch: launch only the async calls required by the unknown result. + frame.DispatchPendingAsyncCalls(unk.IDs()) -// Setup initializes a pooled Activation object with the map input. -func (p *evalActivationPool) Setup(vars map[string]any) *evalActivation { - a := p.Pool.Get().(*evalActivation) - a.vars = vars - return a -} + // The result depends on one or more unresolved async calls. Wait for completions and + // re-evaluate according to the configured drain strategy. + var batch []async.Call -func (p *evalActivationPool) Put(value any) { - a := value.(*evalActivation) - for k := range a.lazyVars { - delete(a.lazyVars, k) - } - p.Pool.Put(a) -} + // Wait for at least one completion (or cancellation). + select { + case id := <-completions: + if call := frame.AsyncCall(id); call != nil { + batch = append(batch, call) + } + case <-ctx.Done(): + resCh <- EvalResult{Val: out, EvalDetails: det, Err: ctx.Err()} + return + } + + // Accumulate completions and consult the strategy. + var timer *time.Timer + reevaluate := false + for !reevaluate { + active := frame.ActiveAsyncCalls() + action := p.drainStrategy.NextAction(batch, active) + if action.Reevaluate { + break + } + + var timeoutCh <-chan time.Time + if action.WaitDuration > 0 { + if timer == nil { + timer = time.NewTimer(action.WaitDuration) + } else { + if !timer.Stop() { + select { + case <-timer.C: + default: + } + } + timer.Reset(action.WaitDuration) + } + timeoutCh = timer.C + } + + select { + case id := <-completions: + if call := frame.AsyncCall(id); call != nil { + batch = append(batch, call) + } + case <-timeoutCh: + reevaluate = true + case <-ctx.Done(): + if timer != nil { + timer.Stop() + } + resCh <- EvalResult{Val: out, EvalDetails: det, Err: ctx.Err()} + return + } + } + if timer != nil { + timer.Stop() + } + } + }() -var ( - // activationPool is an internally managed pool of Activation values that wrap map[string]any inputs - activationPool = newEvalActivationPool() + return resCh +} - // ctxActivationPool is an internally managed pool of Activation values that expose a special #interrupted variable - ctxActivationPool = newCtxEvalActivationPool() -) +// errAsyncRequiresConcurrentEval is returned by the synchronous entry points (Eval, ContextEval) +// when the expression contains asynchronous function calls, which only ConcurrentEval can resolve. +var errAsyncRequiresConcurrentEval = errors.New( + "expression contains asynchronous function calls; use ConcurrentEval") diff --git a/vendor/github.com/google/cel-go/cel/prompt.go b/vendor/github.com/google/cel-go/cel/prompt.go index 929a26f91..f59934827 100644 --- a/vendor/github.com/google/cel-go/cel/prompt.go +++ b/vendor/github.com/google/cel-go/cel/prompt.go @@ -23,15 +23,48 @@ import ( "github.com/google/cel-go/common" "github.com/google/cel-go/common/operators" "github.com/google/cel-go/common/overloads" + "github.com/google/cel-go/common/types" ) //go:embed templates/authoring.tmpl var authoringPrompt string +// splitImpl splits a string into a list of strings. +// +// Normalizes extracted comments (trim common prefix whitespace and extra trailing newlines). +func splitImpl(str string) []string { + str = strings.TrimRight(str, " \n\t\r") + out := strings.Split(str, "\n") + if len(out) == 0 { + return nil + } + negative := strings.TrimLeft(out[0], " \t") + lenNegative := len(negative) + lenOut := len(out[0]) + if lenNegative == lenOut { + return out + } + prefix := out[0][:lenOut-lenNegative] + trimmed := make([]string, len(out)) + for i, line := range out { + if line == "" { + trimmed[i] = "" + continue + } + if !strings.HasPrefix(line, prefix) { + return out + } + trimmed[i] = strings.TrimPrefix(line, prefix) + } + + return trimmed +} + // AuthoringPrompt creates a prompt template from a CEL environment for the purpose of AI-assisted authoring. func AuthoringPrompt(env *Env) (*Prompt, error) { funcMap := template.FuncMap{ - "split": func(str string) []string { return strings.Split(str, "\n") }, + "split": splitImpl, + "newlineToSpace": func(str string) string { return strings.ReplaceAll(str, "\n", " ") }, } tmpl := template.New("cel").Funcs(funcMap) tmpl, err := tmpl.Parse(authoringPrompt) @@ -47,6 +80,17 @@ func AuthoringPrompt(env *Env) (*Prompt, error) { }, nil } +// AuthoringPromptWithFieldPaths creates a prompt template from a CEL environment for the purpose of AI-assisted authoring. +// Includes documentation for all of the reachable field paths in the environment. +func AuthoringPromptWithFieldPaths(env *Env) (*Prompt, error) { + p, err := AuthoringPrompt(env) + if err != nil { + return nil, err + } + p.fieldPaths = true + return p, nil +} + // Prompt represents the core components of an LLM prompt based on a CEL environment. // // All fields of the prompt may be overwritten / modified with support for rendering the @@ -64,14 +108,22 @@ type Prompt struct { // tmpl is the text template base-configuration for rendering text. tmpl *template.Template + // fieldPaths is a flag to include reachable field paths in the prompt. + fieldPaths bool + // env reference used to collect variables, functions, and macros available to the prompt. env *Env } +type promptVariable struct { + *common.Doc + FieldPaths []*common.Doc +} + type promptInst struct { *Prompt - Variables []*common.Doc + Variables []*promptVariable Macros []*common.Doc Functions []*common.Doc UserPrompt string @@ -79,11 +131,36 @@ type promptInst struct { // Render renders the user prompt with the associated context from the prompt template // for use with LLM generators. +// +// User-supplied input is passed as template data via the UserPrompt field, which +// Go's text/template renders as a literal string value. Template action delimiters +// such as {{.Persona}} in the user prompt are never evaluated as template directives +// because text/template only executes directives present in the template definition +// itself, not in data values interpolated at render time. func (p *Prompt) Render(userPrompt string) string { var buffer strings.Builder - vars := make([]*common.Doc, len(p.env.Variables())) + vars := make([]*promptVariable, len(p.env.Variables())) for i, v := range p.env.Variables() { - vars[i] = v.Documentation() + vars[i] = &promptVariable{Doc: v.Documentation()} + if p.fieldPaths && v.Type().Kind() == types.StructKind { + var fieldPaths []*common.Doc + + paths := fieldPathsForType(p.env.CELTypeProvider(), v.Name(), v.Type()) + if len(paths) < 2 { + paths = nil + } else { + // First path is the variable which is already documented. + paths = paths[1:] + } + for _, path := range paths { + fieldPaths = append(fieldPaths, path.Documentation()) + } + + sort.SliceStable(fieldPaths, func(i, j int) bool { + return fieldPaths[i].Name < fieldPaths[j].Name + }) + vars[i].FieldPaths = fieldPaths + } } sort.SliceStable(vars, func(i, j int) bool { return vars[i].Name < vars[j].Name @@ -107,7 +184,8 @@ func (p *Prompt) Render(userPrompt string) string { Variables: vars, Macros: macs, Functions: funcs, - UserPrompt: userPrompt} + UserPrompt: userPrompt, + } p.tmpl.Execute(&buffer, inst) return buffer.String() } diff --git a/vendor/github.com/google/cel-go/cel/templates/authoring.tmpl b/vendor/github.com/google/cel-go/cel/templates/authoring.tmpl index d6b3da5c6..a921df9b0 100644 --- a/vendor/github.com/google/cel-go/cel/templates/authoring.tmpl +++ b/vendor/github.com/google/cel-go/cel/templates/authoring.tmpl @@ -1,8 +1,29 @@ -{{define "variable"}}{{.Name}} is a {{.Type}} +{{define "fieldPath" }} + * path: `{{.Name}}` + type: `{{.Type}}` + {{- if .Description }} + description: +{{range split .Description }} {{.}} +{{end}} +{{- end -}} +{{- end -}} + +{{define "variable" -}} +* name: `{{.Name}}` + type: `{{.Type}}` + {{- if .Description}} + description: +{{range split .Description}} {{.}} +{{end -}} +{{- end -}} +{{- if .FieldPaths }} + attributes: +{{- range .FieldPaths }}{{ template "fieldPath" . }}{{end}} +{{- end -}} {{- end -}} {{define "macro" -}} -{{.Name}} macro{{if .Description}} - {{range split .Description}}{{.}} {{end}} +{{.Name}} macro{{if .Description}} - {{newlineToSpace .Description}} {{end}} {{range .Children}}{{range split .Description}} {{.}} {{end}} @@ -18,7 +39,7 @@ {{- end -}} {{define "function" -}} -{{.Name}}{{if .Description}} - {{range split .Description}}{{.}} {{end}} +{{.Name}}{{if .Description}} - {{newlineToSpace .Description}} {{end}} {{range .Children}}{{template "overload" .}}{{end}} {{- end -}} @@ -32,25 +53,26 @@ Only use the following variables, macros, and functions in expressions. {{if .Variables}} Variables: -{{range .Variables}}* {{template "variable" .}} +{{range .Variables -}} +{{template "variable" .}} {{end -}} +{{- end -}} -{{end -}} {{if .Macros}} Macros: {{range .Macros}}* {{template "macro" .}} {{end -}} - {{end -}} + {{if .Functions}} Functions: {{range .Functions}}* {{template "function" .}} {{end -}} - -{{end -}} {{- end -}} +{{- end -}} + {{.GeneralUsage}} {{.UserPrompt}} diff --git a/vendor/github.com/google/cel-go/cel/validator.go b/vendor/github.com/google/cel-go/cel/validator.go index 5f06b2dd5..229defe79 100644 --- a/vendor/github.com/google/cel-go/cel/validator.go +++ b/vendor/github.com/google/cel-go/cel/validator.go @@ -15,6 +15,7 @@ package cel import ( + "context" "fmt" "reflect" "regexp" @@ -22,14 +23,17 @@ import ( "github.com/google/cel-go/common/ast" "github.com/google/cel-go/common/env" "github.com/google/cel-go/common/overloads" + "github.com/google/cel-go/common/types" ) const ( - durationValidatorName = "cel.validator.duration" - regexValidatorName = "cel.validator.matches" - timestampValidatorName = "cel.validator.timestamp" - homogeneousValidatorName = "cel.validator.homogeneous_literals" - nestingLimitValidatorName = "cel.validator.comprehension_nesting_limit" + durationValidatorName = "cel.validator.duration" + regexValidatorName = "cel.validator.matches" + timestampValidatorName = "cel.validator.timestamp" + homogeneousValidatorName = "cel.validator.homogeneous_literals" + nestingLimitValidatorName = "cel.validator.comprehension_nesting_limit" + bindNestingLimitValidatorName = "cel.validator.bind_nesting_limit" + regexProgramSizeLimitValidatorName = "cel.validator.regex_program_size_limit" // HomogeneousAggregateLiteralExemptFunctions is the ValidatorConfig key used to configure // the set of function names which are exempt from homogeneous type checks. The expected type @@ -44,13 +48,25 @@ const ( var ( astValidatorFactories = map[string]ASTValidatorFactory{ nestingLimitValidatorName: func(val *env.Validator) (ASTValidator, error) { - if limit, found := val.ConfigValue("limit"); found { - if val, isInt := limit.(int); isInt { - return ValidateComprehensionNestingLimit(val), nil - } - return nil, fmt.Errorf("invalid validator: %s unsupported limit type: %v", nestingLimitValidatorName, limit) + limit, err := validatorIntConfig(val, "limit") + if err != nil { + return nil, err + } + return ValidateComprehensionNestingLimit(limit), nil + }, + bindNestingLimitValidatorName: func(val *env.Validator) (ASTValidator, error) { + limit, err := validatorIntConfig(val, "limit") + if err != nil { + return nil, err } - return nil, fmt.Errorf("invalid validator: %s missing limit", nestingLimitValidatorName) + return ValidateBindNestingLimit(limit), nil + }, + regexProgramSizeLimitValidatorName: func(val *env.Validator) (ASTValidator, error) { + limit, err := validatorIntConfig(val, "limit") + if err != nil { + return nil, err + } + return ValidateRegexProgramSizeLimit(limit), nil }, durationValidatorName: func(*env.Validator) (ASTValidator, error) { return ValidateDurationLiterals(), nil @@ -72,12 +88,20 @@ type ASTValidatorFactory func(*env.Validator) (ASTValidator, error) // ASTValidators configures a set of ASTValidator instances into the target environment. // -// Validators are applied in the order in which the are specified and are treated as singletons. -// The same ASTValidator with a given name will not be applied more than once. +// Validators are applied in the order in which they are specified. +// If an ASTValidator with the same name is already configured, it will be replaced. func ASTValidators(validators ...ASTValidator) EnvOption { return func(e *Env) (*Env, error) { for _, v := range validators { - if !e.HasValidator(v.Name()) { + found := false + for i, existing := range e.validators { + if existing.Name() == v.Name() { + e.validators[i] = v + found = true + break + } + } + if !found { e.validators = append(e.validators, v) } } @@ -224,6 +248,18 @@ func ValidateComprehensionNestingLimit(limit int) ASTValidator { return nestingLimitValidator{limit: limit} } +// ValidateBindNestingLimit ensures that cel.bind() macro nesting does not exceed the specified limit. +// +// This validator can be useful for preventing arbitrarily nested cel.bind() macro calls. +func ValidateBindNestingLimit(limit int) ASTValidator { + return bindNestingLimitValidator{limit: limit} +} + +// ValidateRegexProgramSizeLimit ensures that regex pattern literals do not exceed the specified regex program size limit. +func ValidateRegexProgramSizeLimit(limit int) ASTValidator { + return regexProgramSizeLimitValidator{limit: limit} +} + type argChecker func(env *Env, call, arg ast.Expr) error func newFormatValidator(funcName string, argNum int, check argChecker) formatValidator { @@ -276,8 +312,9 @@ func evalCall(env *Env, call, arg ast.Expr) error { if err != nil { return err } - _, _, err = prg.Eval(NoVars()) - return err + resCh := prg.ConcurrentEval(context.Background(), NoVars()) + res := <-resCh + return res.Err } func compileRegex(_ *Env, _, arg ast.Expr) error { @@ -422,8 +459,7 @@ func (v nestingLimitValidator) Validate(e *Env, _ ValidatorConfig, a *ast.AST, i } // When the comprehension has an empty range, continue to the next ancestor // as this comprehension does not have any associated cost. - iterRange := e.AsComprehension().IterRange() - if iterRange.Kind() == ast.ListKind && iterRange.AsList().Size() == 0 { + if isEmptyRangeComprehension(e) { e, hasParent = e.Parent() continue } @@ -437,3 +473,145 @@ func (v nestingLimitValidator) Validate(e *Env, _ ValidatorConfig, a *ast.AST, i } } } + +type bindNestingLimitValidator struct { + limit int +} + +// Name returns the name of the cel.bind nesting limit validator. +func (v bindNestingLimitValidator) Name() string { + return bindNestingLimitValidatorName +} + +// ToConfig converts the ASTValidator to an env.Validator specifying the validator name and the nesting limit +// as an integer value: {"limit": int} +func (v bindNestingLimitValidator) ToConfig() *env.Validator { + return env.NewValidator(v.Name()).SetConfig(map[string]any{"limit": v.limit}) +} + +type regexProgramSizeLimitValidator struct { + limit int +} + +// Name returns the name of the regex program size limit validator. +func (v regexProgramSizeLimitValidator) Name() string { + return regexProgramSizeLimitValidatorName +} + +// ToConfig converts the ASTValidator to an env.Validator specifying the validator name and the limit. +func (v regexProgramSizeLimitValidator) ToConfig() *env.Validator { + return env.NewValidator(v.Name()).SetConfig(map[string]any{"limit": v.limit}) +} + +// Validate implements the ASTValidator interface method. +func (v bindNestingLimitValidator) Validate(e *Env, _ ValidatorConfig, a *ast.AST, iss *Issues) { + root := ast.NavigateAST(a) + comprehensions := ast.MatchDescendants(root, ast.KindMatcher(ast.ComprehensionKind)) + var celBinds []ast.NavigableExpr + for _, comp := range comprehensions { + if isCelBind(comp) { + celBinds = append(celBinds, comp) + } + } + if len(celBinds) <= v.limit { + return + } + for _, comp := range celBinds { + count := 0 + e := comp + hasParent := true + for hasParent { + if isCelBind(e) { + count++ + if count > v.limit { + iss.ReportErrorAtID(comp.ID(), "cel.bind exceeds nesting limit") + break + } + } + e, hasParent = e.Parent() + } + } +} + +func (v regexProgramSizeLimitValidator) Validate(e *Env, _ ValidatorConfig, a *ast.AST, iss *Issues) { + if v.limit <= 0 { + return + } + root := ast.NavigateAST(a) + callExprs := ast.MatchDescendants(root, ast.KindMatcher(ast.CallKind)) + for _, call := range callExprs { + c := call.AsCall() + fn := c.FunctionName() + if !isRegexFunctionName(fn) { + continue + } + args := c.Args() + var regexArgIndex int + if (fn == overloads.Matches || fn == "matches") && c.Target() != nil { + regexArgIndex = 0 + } else { + regexArgIndex = 1 + } + if len(args) <= regexArgIndex { + continue + } + arg := args[regexArgIndex] + if arg.Kind() != ast.LiteralKind { + continue + } + pattern, ok := arg.AsLiteral().Value().(string) + if !ok { + continue + } + sz, err := types.RegexProgramSize(pattern) + if err != nil { + // Invalid regex literals are handled in a different validator. + continue + } + if sz > v.limit { + iss.ReportErrorAtID(arg.ID(), "regex program size %d exceeds limit of %d", sz, v.limit) + } + } +} + +func isEmptyRangeComprehension(e ast.NavigableExpr) bool { + if e.Kind() != ast.ComprehensionKind { + return false + } + iterRange := e.AsComprehension().IterRange() + return iterRange.Kind() == ast.ListKind && iterRange.AsList().Size() == 0 +} + +func isCelBind(e ast.NavigableExpr) bool { + if !isEmptyRangeComprehension(e) { + return false + } + compre := e.AsComprehension() + loopCond := compre.LoopCondition() + loopStep := compre.LoopStep() + return compre.IterVar() == unusedIterVar && + loopCond.Kind() == ast.LiteralKind && loopCond.AsLiteral().Value() == false && + loopStep.Kind() == ast.IdentKind && loopStep.AsIdent() == compre.AccuVar() +} + +func isRegexFunctionName(fn string) bool { + return fn == overloads.Matches || fn == "matches" || fn == "regex.extract" || fn == "regex.extractAll" || fn == "regex.replace" +} + +func validatorIntConfig(val *env.Validator, configKey string) (int, error) { + if limit, found := val.ConfigValue(configKey); found { + // In case of protos, config value is of type google.protobuf.Value, which numeric values are always a double. + if v, isDouble := limit.(float64); isDouble { + if v != float64(int64(v)) { + return 0, fmt.Errorf("invalid validator: %s, %s value is not a whole number: %v", val.Name, configKey, limit) + } + return int(v), nil + } + + if v, isInt := limit.(int); isInt { + return v, nil + } + return 0, fmt.Errorf("invalid validator: %s unsupported %s type: %v", val.Name, configKey, limit) + } + return 0, fmt.Errorf("invalid validator: %s missing %s", val.Name, configKey) +} diff --git a/vendor/github.com/google/cel-go/checker/checker.go b/vendor/github.com/google/cel-go/checker/checker.go index 0057c16cc..42d27a428 100644 --- a/vendor/github.com/google/cel-go/checker/checker.go +++ b/vendor/github.com/google/cel-go/checker/checker.go @@ -19,6 +19,8 @@ package checker import ( "fmt" "reflect" + "slices" + "strings" "github.com/google/cel-go/common" "github.com/google/cel-go/common/ast" @@ -65,6 +67,15 @@ func Check(parsed *ast.AST, source common.Source, env *Env) (*ast.AST, *common.E for id, t := range c.TypeMap() { c.SetType(id, substitute(c.mappings, t, true)) } + // Remove source info for IDs without a corresponding AST node. This can happen because + // check() deletes some nodes while rewriting the AST. For example the Select operand is + // deleted when a variable reference is replaced with a Ident expression. + c.AST.ClearUnusedIDs() + if env.jsonFieldNames { + c.AST.SourceInfo().AddExtension( + ast.NewExtension("json_name", ast.NewExtensionVersion(1, 1), ast.ComponentRuntime), + ) + } return c.AST, errs } @@ -104,11 +115,15 @@ func (c *checker) check(e ast.Expr) { func (c *checker) checkIdent(e ast.Expr) { identName := e.AsIdent() // Check to see if the identifier is declared. - if ident := c.env.LookupIdent(identName); ident != nil { + if ident := c.env.resolveSimpleIdent(identName); ident != nil { + name := strings.TrimPrefix(ident.Name(), ".") + if ident.requiresDisambiguation { + name = "." + name + } c.setType(e, ident.Type()) - c.setReference(e, ast.NewIdentReference(ident.Name(), ident.Value())) + c.setReference(e, ast.NewIdentReference(name, ident.Value())) // Overwrite the identifier with its fully qualified name. - e.SetKindCase(c.NewIdent(e.ID(), ident.Name())) + e.SetKindCase(c.NewIdent(e.ID(), name)) return } @@ -119,18 +134,22 @@ func (c *checker) checkIdent(e ast.Expr) { func (c *checker) checkSelect(e ast.Expr) { sel := e.AsSelect() // Before traversing down the tree, try to interpret as qualified name. - qname, found := containers.ToQualifiedName(e) + qualifiers, found := c.computeQualifiers(e) if found { - ident := c.env.LookupIdent(qname) + ident := c.env.resolveQualifiedIdent(qualifiers...) if ident != nil { // We don't check for a TestOnly expression here since the `found` result is // always going to be false for TestOnly expressions. // Rewrite the node to be a variable reference to the resolved fully-qualified // variable name. + name := ident.Name() + if ident.requiresDisambiguation { + name = "." + name + } c.setType(e, ident.Type()) - c.setReference(e, ast.NewIdentReference(ident.Name(), ident.Value())) - e.SetKindCase(c.NewIdent(e.ID(), ident.Name())) + c.setReference(e, ast.NewIdentReference(name, ident.Value())) + e.SetKindCase(c.NewIdent(e.ID(), name)) return } } @@ -142,6 +161,29 @@ func (c *checker) checkSelect(e ast.Expr) { c.setType(e, substitute(c.mappings, resultType, false)) } +// computeQualifiers computes the qualified names parts of a select expression. +func (c *checker) computeQualifiers(e ast.Expr) ([]string, bool) { + var qualifiers []string + for e.Kind() == ast.SelectKind { + sel := e.AsSelect() + // test only expressions are not considered for qualified name selection. + if sel.IsTestOnly() { + return qualifiers, false + } + // otherwise append the select field name to the qualifier list (reverse order) + qualifiers = append(qualifiers, sel.FieldName()) + e = sel.Operand() + // If the next operand is an identifier, then append it, reverse the name sequence + // and return it to the caller.s + if e.Kind() == ast.IdentKind { + qualifiers = append(qualifiers, e.AsIdent()) + slices.Reverse(qualifiers) + return qualifiers, true + } + } + return qualifiers, false +} + func (c *checker) checkOptSelect(e ast.Expr) { // Collect metadata related to the opt select call packaged by the parser. call := e.AsCall() @@ -234,7 +276,7 @@ func (c *checker) checkCall(e ast.Expr) { // Regular static call with simple name. if !call.IsMemberFunction() { // Check for the existence of the function. - fn := c.env.LookupFunction(fnName) + fn := c.env.lookupFunction(fnName) if fn == nil { c.errors.undeclaredReference(e.ID(), c.location(e), c.env.container.Name(), fnName) c.setType(e, types.ErrorType) @@ -256,7 +298,7 @@ func (c *checker) checkCall(e ast.Expr) { qualifiedPrefix, maybeQualified := containers.ToQualifiedName(target) if maybeQualified { maybeQualifiedName := qualifiedPrefix + "." + fnName - fn := c.env.LookupFunction(maybeQualifiedName) + fn := c.env.lookupFunction(maybeQualifiedName) if fn != nil { // The function name is namespaced and so preserving the target operand would // be an inaccurate representation of the desired evaluation behavior. @@ -269,7 +311,7 @@ func (c *checker) checkCall(e ast.Expr) { // Regular instance call. c.check(target) - fn := c.env.LookupFunction(fnName) + fn := c.env.lookupFunction(fnName) // Function found, attempt overload resolution. if fn != nil { c.resolveOverloadOrError(e, fn, target, args) @@ -441,7 +483,7 @@ func (c *checker) checkCreateStruct(e ast.Expr) { msgVal := e.AsStruct() // Determine the type of the message. resultType := types.ErrorType - ident := c.env.LookupIdent(msgVal.TypeName()) + ident := c.env.resolveTypeIdent(msgVal.TypeName()) if ident == nil { c.errors.undeclaredReference( e.ID(), c.location(e), c.env.container.Name(), msgVal.TypeName()) @@ -681,6 +723,9 @@ func (c *checker) lookupFieldType(exprID int64, structType, fieldName string) (* } if ft, found := c.env.provider.FindStructFieldType(structType, fieldName); found { + if c.env.jsonFieldNames && !ft.IsJSONField { + c.errors.undefinedField(exprID, c.locationByID(exprID), fieldName) + } return ft.Type, found } diff --git a/vendor/github.com/google/cel-go/checker/cost.go b/vendor/github.com/google/cel-go/checker/cost.go index 5bc6318ed..3d7dd7ec4 100644 --- a/vendor/github.com/google/cel-go/checker/cost.go +++ b/vendor/github.com/google/cel-go/checker/cost.go @@ -159,6 +159,11 @@ func (se SizeEstimate) Union(size SizeEstimate) SizeEstimate { return result } +// AsCost converts a size estimates to an equivalent cost estimate. +func (se SizeEstimate) AsCost() CostEstimate { + return se.MultiplyByCostFactor(1) +} + // CostEstimate represents an estimated cost range and provides add and multiply operations // that do not overflow. type CostEstimate struct { @@ -786,18 +791,26 @@ func (c *coster) functionCost(e ast.Expr, function, overloadID string, target *A return CallEstimate{CostEstimate: c.sizeOrUnknown(args[1]).MultiplyByCostFactor(1).Add(argCostSum())} } // O(nm) functions - case overloads.MatchesString: + case overloads.Matches, overloads.MatchesString: // https://swtch.com/~rsc/regexp/regexp1.html applies to RE2 implementation supported by CEL - if target != nil && len(args) == 1 { + var strNode, regexNode AstNode + if overloadID == overloads.MatchesString && target != nil && len(args) == 1 { + strNode = *target + regexNode = args[0] + } else if overloadID == overloads.Matches && target == nil && len(args) == 2 { + strNode = args[0] + regexNode = args[1] + } + if strNode != nil && regexNode != nil { // 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. - strCost := c.sizeOrUnknown(*target).Add(SizeEstimate{Min: 1, Max: 1}).MultiplyByCostFactor(common.StringTraversalCostFactor) + strCost := c.sizeOrUnknown(strNode).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 // 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). // For now, we're making a guess that each expression in a regex is typically at least 4 chars // in length. - regexCost := c.sizeOrUnknown(args[0]).MultiplyByCostFactor(common.RegexStringLengthCostFactor) + regexCost := c.sizeOrUnknown(regexNode).MultiplyByCostFactor(common.RegexStringLengthCostFactor) return CallEstimate{CostEstimate: strCost.Multiply(regexCost).Add(argCostSum())} } case overloads.ContainsString: diff --git a/vendor/github.com/google/cel-go/checker/env.go b/vendor/github.com/google/cel-go/checker/env.go index d5ac05014..9c3de9517 100644 --- a/vendor/github.com/google/cel-go/checker/env.go +++ b/vendor/github.com/google/cel-go/checker/env.go @@ -74,6 +74,7 @@ type Env struct { declarations *Scopes aggLitElemType aggregateLiteralElementType filteredOverloadIDs map[string]struct{} + jsonFieldNames bool } // NewEnv returns a new *Env with the given parameters. @@ -96,7 +97,7 @@ func NewEnv(container *containers.Container, provider types.Provider, opts ...Op filteredOverloadIDs = make(map[string]struct{}) } if envOptions.validatedDeclarations != nil { - declarations = envOptions.validatedDeclarations.Copy() + declarations = envOptions.validatedDeclarations.PushInherited() } return &Env{ container: container, @@ -104,6 +105,7 @@ func NewEnv(container *containers.Container, provider types.Provider, opts ...Op declarations: declarations, aggLitElemType: aggLitElemType, filteredOverloadIDs: filteredOverloadIDs, + jsonFieldNames: envOptions.jsonFieldNames, }, nil } @@ -129,45 +131,111 @@ func (e *Env) AddFunctions(declarations ...*decls.FunctionDecl) error { return formatError(errMsgs) } -// LookupIdent returns a Decl proto for typeName as an identifier in the Env. -// Returns nil if no such identifier is found in the Env. -func (e *Env) LookupIdent(name string) *decls.VariableDecl { +// newAttrResolution creates a new attribute resolution value. +func newAttrResolution(ident *decls.VariableDecl, requiresDisambiguation bool) *attributeResolution { + return &attributeResolution{ + VariableDecl: ident, + requiresDisambiguation: requiresDisambiguation, + } +} + +// attributeResolution wraps an existing variable and denotes whether disambiguation is needed +// during variable resolution. +type attributeResolution struct { + *decls.VariableDecl + + // requiresDisambiguation indicates the variable name should be dot-prefixed. + requiresDisambiguation bool +} + +// resolveSimpleIdent determines the resolved attribute for a single identifier. +func (e *Env) resolveSimpleIdent(name string) *attributeResolution { + local := e.lookupLocalIdent(name) + if local != nil && !strings.HasPrefix(name, ".") { + return newAttrResolution(local, false) + } for _, candidate := range e.container.ResolveCandidateNames(name) { - if ident := e.declarations.FindIdent(candidate); ident != nil { - return ident + if ident := e.lookupGlobalIdent(candidate); ident != nil { + return newAttrResolution(ident, local != nil) } + } + return nil +} - // Next try to import the name as a reference to a message type. If found, - // the declaration is added to the outest (global) scope of the - // environment, so next time we can access it faster. - if t, found := e.provider.FindStructType(candidate); found { - decl := decls.NewVariable(candidate, t) - e.declarations.AddIdent(decl) - return decl +// resolveQualifiedIdent determines the resolved attribute for a qualified identifier. +func (e *Env) resolveQualifiedIdent(qualifiers ...string) *attributeResolution { + if len(qualifiers) == 1 { + return e.resolveSimpleIdent(qualifiers[0]) + } + local := e.lookupLocalIdent(qualifiers[0]) + if local != nil && !strings.HasPrefix(qualifiers[0], ".") { + // this should resolve through a field selection rather than a qualified identifier + return nil + } + // The qualifiers are concatenated together to indicate the qualified name to search + // for as a global identifier. Since select expressions are resolved from leaf to root + // if the fully concatenated string doesn't match a global identifier, indicate that + // no variable was found to continue the traversal up to the next simpler name. + varName := strings.Join(qualifiers, ".") + for _, candidate := range e.container.ResolveCandidateNames(varName) { + if ident := e.lookupGlobalIdent(candidate); ident != nil { + return newAttrResolution(ident, local != nil) } + } + return nil +} +// resolveTypeIdent returns a Decl proto for typeName as an identifier in the Env. +// Returns nil if no such identifier is found in the Env. +func (e *Env) resolveTypeIdent(name string) *decls.VariableDecl { + for _, candidate := range e.container.ResolveCandidateNames(name) { + // Try to import the name as a reference to a message type. if i, found := e.provider.FindIdent(candidate); found { if t, ok := i.(*types.Type); ok { - decl := decls.NewVariable(candidate, types.NewTypeTypeWithParam(t)) - e.declarations.AddIdent(decl) - return decl + return decls.NewVariable(candidate, types.NewTypeTypeWithParam(t)) } } + // Next, try to find the struct type. + if t, found := e.provider.FindStructType(candidate); found { + return decls.NewVariable(candidate, t) + } + } + return nil +} - // Next try to import this as an enum value by splitting the name in a type prefix and - // the enum inside. - if enumValue := e.provider.EnumValue(candidate); enumValue.Type() != types.ErrType { - decl := decls.NewConstant(candidate, types.IntType, enumValue) - e.declarations.AddIdent(decl) - return decl +// lookupLocalIdent finds the variable candidate in a local scope, returning nil if +// the candidate variable name is not a local variable. +func (e *Env) lookupLocalIdent(candidate string) *decls.VariableDecl { + return e.declarations.FindLocalIdent(candidate) +} + +// lookupGlobalIdent finds a candidate variable name in the root scope, returning +// nil if the identifier is not in the global scope. +func (e *Env) lookupGlobalIdent(candidate string) *decls.VariableDecl { + // Try to resolve the global identifier first. + if ident := e.declarations.FindGlobalIdent(candidate); ident != nil { + return ident + } + // Next try to import the name as a reference to a message type. + if i, found := e.provider.FindIdent(candidate); found { + if t, ok := i.(*types.Type); ok { + return decls.NewVariable(candidate, types.NewTypeTypeWithParam(t)) } } + if t, found := e.provider.FindStructType(candidate); found { + return decls.NewVariable(candidate, t) + } + // Next try to import this as an enum value by splitting the name in a type prefix and + // the enum inside. + if enumValue := e.provider.EnumValue(candidate); enumValue.Type() != types.ErrType { + return decls.NewConstant(candidate, types.IntType, enumValue) + } return nil } -// LookupFunction returns a Decl proto for typeName as a function in env. +// lookupFunction returns a Decl proto for typeName as a function in env. // Returns nil if no such function is found in env. -func (e *Env) LookupFunction(name string) *decls.FunctionDecl { +func (e *Env) lookupFunction(name string) *decls.FunctionDecl { for _, candidate := range e.container.ResolveCandidateNames(name) { if fn := e.declarations.FindFunction(candidate); fn != nil { return fn @@ -207,12 +275,31 @@ func (e *Env) setFunction(fn *decls.FunctionDecl) []errorMsg { return errMsgs } +func maybeMergeConstant(a *decls.VariableDecl, b *decls.VariableDecl) (*decls.VariableDecl, errorMsg) { + if b.Value() != nil { + if a.Value() == nil { + return b, "" + } + eq, ok := a.Value().Equal(b.Value()).Value().(bool) + if ok && eq { + return a, "" + } + return nil, constantConflictError(b.Name()) + } + return a, "" +} + // addIdent adds the Decl to the declarations in the Env. // Returns a non-empty errorMsg if the identifier is already declared in the scope. func (e *Env) addIdent(decl *decls.VariableDecl) errorMsg { current := e.declarations.FindIdentInScope(decl.Name()) if current != nil { if current.DeclarationIsEquivalent(decl) { + decl, errMsg := maybeMergeConstant(current, decl) + if errMsg != "" { + return errMsg + } + e.declarations.AddIdent(decl) return "" } return overlappingIdentifierError(decl.Name()) @@ -259,6 +346,10 @@ func (e *Env) exitScope() *Env { // may be accumulated into an error at a later point in execution. type errorMsg string +func constantConflictError(name string) errorMsg { + return errorMsg(fmt.Sprintf("conflicting constant definitions for name '%s'", name)) +} + func overlappingIdentifierError(name string) errorMsg { return errorMsg(fmt.Sprintf("overlapping identifier for name '%s'", name)) } diff --git a/vendor/github.com/google/cel-go/checker/options.go b/vendor/github.com/google/cel-go/checker/options.go index 0560c3813..10d3bcbc0 100644 --- a/vendor/github.com/google/cel-go/checker/options.go +++ b/vendor/github.com/google/cel-go/checker/options.go @@ -18,6 +18,7 @@ type options struct { crossTypeNumericComparisons bool homogeneousAggregateLiterals bool validatedDeclarations *Scopes + jsonFieldNames bool } // Option is a functional option for configuring the type-checker @@ -32,11 +33,20 @@ func CrossTypeNumericComparisons(enabled bool) Option { } } -// ValidatedDeclarations provides a references to validated declarations which will be copied -// into new checker instances. +// ValidatedDeclarations provides a reference to validated declarations which will be inherited +// as a parent scope without copying. func ValidatedDeclarations(env *Env) Option { return func(opts *options) error { opts.validatedDeclarations = env.validatedDeclarations() return nil } } + +// JSONFieldNames enables the use of json names instead of the standard protobuf snake_case field names +func JSONFieldNames(enabled bool) Option { + return func(opts *options) error { + opts.jsonFieldNames = enabled + return nil + } +} + diff --git a/vendor/github.com/google/cel-go/checker/scopes.go b/vendor/github.com/google/cel-go/checker/scopes.go index 8bb73ddb6..2fcfdf29e 100644 --- a/vendor/github.com/google/cel-go/checker/scopes.go +++ b/vendor/github.com/google/cel-go/checker/scopes.go @@ -15,6 +15,8 @@ package checker import ( + "strings" + "github.com/google/cel-go/common/decls" ) @@ -23,8 +25,9 @@ import ( // Each Groups value is a mapping of names to Decls in the ident and function namespaces. // Lookups are performed such that bindings in inner scopes shadow those in outer scopes. type Scopes struct { - parent *Scopes - scopes *Group + parent *Scopes + inherited *Scopes + scopes *Group } // newScopes creates a new, empty Scopes. @@ -35,19 +38,6 @@ func newScopes() *Scopes { } } -// Copy creates a copy of the current Scopes values, including a copy of its parent if non-nil. -func (s *Scopes) Copy() *Scopes { - cpy := newScopes() - if s == nil { - return cpy - } - if s.parent != nil { - cpy.parent = s.parent.Copy() - } - cpy.scopes = s.scopes.copy() - return cpy -} - // Push creates a new Scopes value which references the current Scope as its parent. func (s *Scopes) Push() *Scopes { return &Scopes{ @@ -56,6 +46,14 @@ func (s *Scopes) Push() *Scopes { } } +// PushInherited creates a new Scopes value which references the current Scope as its inherited parent. +func (s *Scopes) PushInherited() *Scopes { + return &Scopes{ + inherited: s, + scopes: newGroup(), + } +} + // Pop returns the parent Scopes value for the current scope, or the current scope if the parent // is nil. func (s *Scopes) Pop() *Scopes { @@ -72,26 +70,40 @@ func (s *Scopes) AddIdent(decl *decls.VariableDecl) { s.scopes.idents[decl.Name()] = decl } -// FindIdent finds the first ident Decl with a matching name in Scopes, or nil if one cannot be -// found. -// Note: The search is performed from innermost to outermost. -func (s *Scopes) FindIdent(name string) *decls.VariableDecl { +// FindIdentInScope finds the first ident Decl with a matching name in the current Scopes value, or +// nil if one does not exist. +// Note: The search is only performed on the current scope and does not search outer scopes. +func (s *Scopes) FindIdentInScope(name string) *decls.VariableDecl { + name = strings.TrimPrefix(name, ".") if ident, found := s.scopes.idents[name]; found { return ident } - if s.parent != nil { - return s.parent.FindIdent(name) - } return nil } -// FindIdentInScope finds the first ident Decl with a matching name in the current Scopes value, or -// nil if one does not exist. -// Note: The search is only performed on the current scope and does not search outer scopes. -func (s *Scopes) FindIdentInScope(name string) *decls.VariableDecl { - if ident, found := s.scopes.idents[name]; found { +// FindLocalIdent finds a locally scoped variable with a given name, ignoring the root scope. +func (s *Scopes) FindLocalIdent(name string) *decls.VariableDecl { + if s == nil || s.parent == nil { + return nil + } + if ident := s.FindIdentInScope(name); ident != nil { return ident } + return s.parent.FindLocalIdent(name) +} + +// FindGlobalIdent finds an identifier in the global scope, ignoring all local scopes. +func (s *Scopes) FindGlobalIdent(name string) *decls.VariableDecl { + scope := s + for scope.parent != nil { + scope = scope.parent + } + if ident := scope.FindIdentInScope(name); ident != nil { + return ident + } + if scope.inherited != nil { + return scope.inherited.FindGlobalIdent(name) + } return nil } @@ -105,11 +117,19 @@ func (s *Scopes) SetFunction(fn *decls.FunctionDecl) { // The search is performed from innermost to outermost. // Returns nil if no such function in Scopes. func (s *Scopes) FindFunction(name string) *decls.FunctionDecl { + name = strings.TrimPrefix(name, ".") if fn, found := s.scopes.functions[name]; found { return fn } if s.parent != nil { - return s.parent.FindFunction(name) + if fn := s.parent.FindFunction(name); fn != nil { + return fn + } + } + if s.inherited != nil { + if fn := s.inherited.FindFunction(name); fn != nil { + return fn + } } return nil } @@ -122,22 +142,6 @@ type Group struct { functions map[string]*decls.FunctionDecl } -// copy creates a new Group instance with a shallow copy of the variables and functions. -// If callers need to mutate the exprpb.Decl definitions for a Function, they should copy-on-write. -func (g *Group) copy() *Group { - cpy := &Group{ - idents: make(map[string]*decls.VariableDecl, len(g.idents)), - functions: make(map[string]*decls.FunctionDecl, len(g.functions)), - } - for n, id := range g.idents { - cpy.idents[n] = id - } - for n, fn := range g.functions { - cpy.functions[n] = fn - } - return cpy -} - // newGroup creates a new Group with empty maps for identifiers and functions. func newGroup() *Group { return &Group{ diff --git a/vendor/github.com/google/cel-go/common/ast/ast.go b/vendor/github.com/google/cel-go/common/ast/ast.go index 62c09cfc6..c8f8f8a02 100644 --- a/vendor/github.com/google/cel-go/common/ast/ast.go +++ b/vendor/github.com/google/cel-go/common/ast/ast.go @@ -16,6 +16,8 @@ package ast import ( + "slices" + "github.com/google/cel-go/common" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" @@ -160,6 +162,34 @@ func MaxID(a *AST) int64 { return visitor.maxID + 1 } +// IDs returns the set of AST node IDs, including macro calls. +func (a *AST) IDs() map[int64]bool { + visitor := make(idVisitor) + PostOrderVisit(a.Expr(), visitor) + for _, call := range a.SourceInfo().MacroCalls() { + PostOrderVisit(call, visitor) + } + return visitor +} + +// NodeCount returns the total number of expression nodes in the AST, including macro calls. +func NodeCount(a *AST) int { + if a == nil { + return 0 + } + return len(a.IDs()) +} + +// ClearUnusedIDs removes IDs not used in the AST or macro calls from SourceInfo. +func (a *AST) ClearUnusedIDs() { + ids := a.IDs() + for id := range a.SourceInfo().OffsetRanges() { + if !ids[id] { + a.SourceInfo().ClearOffsetRange(id) + } + } +} + // Heights computes the heights of all AST expressions and returns a map from expression id to height. func Heights(a *AST) map[int64]int { visitor := make(heightVisitor) @@ -209,6 +239,11 @@ func CopySourceInfo(info *SourceInfo) *SourceInfo { for id, call := range info.macroCalls { callsCopy[id] = defaultFactory.CopyExpr(call) } + var extCopy []Extension + if len(info.extensions) > 0 { + extCopy = make([]Extension, len(info.extensions)) + copy(extCopy, info.extensions) + } return &SourceInfo{ syntax: info.syntax, desc: info.desc, @@ -217,6 +252,7 @@ func CopySourceInfo(info *SourceInfo) *SourceInfo { baseCol: info.baseCol, offsetRanges: rangesCopy, macroCalls: callsCopy, + extensions: extCopy, } } @@ -230,6 +266,26 @@ type SourceInfo struct { baseCol int32 offsetRanges map[int64]OffsetRange macroCalls map[int64]Expr + + // extensions indicate versioned optional features which affect the execution of one or more CEL component. + extensions []Extension +} + +// RenumberIDs performs an in-place update of the expression IDs within the SourceInfo. +func (s *SourceInfo) RenumberIDs(idGen IDGenerator) { + if s == nil { + return + } + oldIDs := []int64{} + for id := range s.offsetRanges { + oldIDs = append(oldIDs, id) + } + slices.Sort(oldIDs) + newRanges := make(map[int64]OffsetRange) + for _, id := range oldIDs { + newRanges[idGen(id)] = s.offsetRanges[id] + } + s.offsetRanges = newRanges } // SyntaxVersion returns the syntax version associated with the text expression. @@ -365,6 +421,12 @@ func (s *SourceInfo) ComputeOffset(line, col int32) int32 { line = s.baseLine + line col = s.baseCol + col } + return s.ComputeOffsetAbsolute(line, col) +} + +// ComputeOffsetAbsolute calculates the 0-based character offset from a 1-based line and 0-based column +// based on the absolute line and column of the SourceInfo. +func (s *SourceInfo) ComputeOffsetAbsolute(line, col int32) int32 { if line == 1 { return col } @@ -375,6 +437,34 @@ func (s *SourceInfo) ComputeOffset(line, col int32) int32 { return offset + col } +// Extensions returns the set of extensions present in the source. +func (s *SourceInfo) Extensions() []Extension { + var extensions []Extension + if s == nil { + return extensions + } + return s.extensions +} + +// HasExtension returns whether the source info contains the extension which satisfies the minimum version requirement. +// +// For an extension to be considered 'present' it must have the same major version as the minVersion and a minor version +// at least as great as the lowest minor version specified. +func (s *SourceInfo) HasExtension(id string, minVersion ExtensionVersion) bool { + for _, ext := range s.Extensions() { + return ext.ID == id && ext.Version.Major == minVersion.Major && ext.Version.Minor >= minVersion.Minor + } + return false +} + +// AddExtension adds an extension record into the SourceInfo. +func (s *SourceInfo) AddExtension(ext Extension) { + if s == nil { + return + } + s.extensions = append(s.extensions, ext) +} + // OffsetRange captures the start and stop positions of a section of text in the input expression. type OffsetRange struct { Start int32 @@ -444,6 +534,53 @@ func (r *ReferenceInfo) Equals(other *ReferenceInfo) bool { return true } +// NewExtension creates an Extension to be recorded on the SourceInfo. +func NewExtension(id string, version ExtensionVersion, components ...ExtensionComponent) Extension { + return Extension{ + ID: id, + Version: version, + Components: components, + } +} + +// Extension represents a versioned, optional feature present in the AST that affects CEL component behavior. +type Extension struct { + // ID indicates the unique name of the extension. + ID string + // Version indicates the major / minor version. + Version ExtensionVersion + // Components enumerates the CEL components affected by the feature. + Components []ExtensionComponent +} + +// NewExtensionVersion creates a new extension version with a major, minor version. +func NewExtensionVersion(major, minor int64) ExtensionVersion { + return ExtensionVersion{Major: major, Minor: minor} +} + +// ExtensionVersion represents a semantic version with a major and minor number. +type ExtensionVersion struct { + // Major version of the extension. + // All versions with the same major number are expected to be compatible with all minor version changes. + Major int64 + + // Minor version of the extension which indicates that some small non-semantic change has been made to + // the extension. + Minor int64 +} + +// ExtensionComponent indicates which CEL component is affected. +type ExtensionComponent int + +const ( + // ComponentParser means the feature affects expression parsing. + ComponentParser ExtensionComponent = iota + 1 + // ComponentTypeChecker means the feature affects type-checking. + ComponentTypeChecker + // ComponentRuntime alters program planning or evaluation of the AST. + ComponentRuntime +) + type maxIDVisitor struct { maxID int64 *baseVisitor @@ -533,3 +670,13 @@ func (hv heightVisitor) maxEntryHeight(entries ...EntryExpr) int { } return max } + +type idVisitor map[int64]bool + +func (v idVisitor) VisitExpr(e Expr) { + v[e.ID()] = true +} + +func (v idVisitor) VisitEntryExpr(e EntryExpr) { + v[e.ID()] = true +} diff --git a/vendor/github.com/google/cel-go/common/ast/conversion.go b/vendor/github.com/google/cel-go/common/ast/conversion.go index 435d8f654..380f8c118 100644 --- a/vendor/github.com/google/cel-go/common/ast/conversion.go +++ b/vendor/github.com/google/cel-go/common/ast/conversion.go @@ -27,6 +27,19 @@ import ( structpb "google.golang.org/protobuf/types/known/structpb" ) +var ( + pbComponentMap = map[exprpb.SourceInfo_Extension_Component]ExtensionComponent{ + exprpb.SourceInfo_Extension_COMPONENT_PARSER: ComponentParser, + exprpb.SourceInfo_Extension_COMPONENT_TYPE_CHECKER: ComponentTypeChecker, + exprpb.SourceInfo_Extension_COMPONENT_RUNTIME: ComponentRuntime, + } + componentPBMap = map[ExtensionComponent]exprpb.SourceInfo_Extension_Component{ + ComponentParser: exprpb.SourceInfo_Extension_COMPONENT_PARSER, + ComponentTypeChecker: exprpb.SourceInfo_Extension_COMPONENT_TYPE_CHECKER, + ComponentRuntime: exprpb.SourceInfo_Extension_COMPONENT_RUNTIME, + } +) + // ToProto converts an AST to a CheckedExpr protobouf. func ToProto(ast *AST) (*exprpb.CheckedExpr, error) { refMap := make(map[int64]*exprpb.Reference, len(ast.ReferenceMap())) @@ -534,6 +547,25 @@ func SourceInfoToProto(info *SourceInfo) (*exprpb.SourceInfo, error) { } sourceInfo.MacroCalls[id] = call } + for _, ext := range info.Extensions() { + var components []exprpb.SourceInfo_Extension_Component + for _, c := range ext.Components { + comp, found := componentPBMap[c] + if found { + components = append(components, comp) + } + } + ver := &exprpb.SourceInfo_Extension_Version{ + Major: ext.Version.Major, + Minor: ext.Version.Minor, + } + pbExt := &exprpb.SourceInfo_Extension{ + Id: ext.ID, + Version: ver, + AffectedComponents: components, + } + sourceInfo.Extensions = append(sourceInfo.Extensions, pbExt) + } return sourceInfo, nil } @@ -556,6 +588,23 @@ func ProtoToSourceInfo(info *exprpb.SourceInfo) (*SourceInfo, error) { } sourceInfo.SetMacroCall(id, call) } + for _, pbExt := range info.GetExtensions() { + var components []ExtensionComponent + for _, c := range pbExt.GetAffectedComponents() { + comp, found := pbComponentMap[*c.Enum()] + if found { + components = append(components, comp) + } + } + sourceInfo.AddExtension(NewExtension( + pbExt.GetId(), + NewExtensionVersion( + pbExt.GetVersion().GetMajor(), + pbExt.GetVersion().GetMinor(), + ), + components..., + )) + } return sourceInfo, nil } diff --git a/vendor/github.com/google/cel-go/common/ast/navigable.go b/vendor/github.com/google/cel-go/common/ast/navigable.go index 13e5777b5..364edfa3a 100644 --- a/vendor/github.com/google/cel-go/common/ast/navigable.go +++ b/vendor/github.com/google/cel-go/common/ast/navigable.go @@ -181,6 +181,29 @@ func PreOrderVisit(expr Expr, visitor Visitor) { visit(expr, visitor, preOrder, 0, 0) } +// ExceedsDepth determines whether the AST contains expressions nested deeper than the specified +// maxDepth. The root expression has depth 0, so a maxDepth of 250 permits expressions nested up +// to and including 250 levels deep. +// +// The traversal is bounded: it descends at most maxDepth+1 levels, so it remains safe to call on +// adversarially deep inputs that could otherwise exhaust the Go stack during later checking or +// planning. A non-positive maxDepth disables the check and returns false. +func ExceedsDepth(a *AST, maxDepth int) bool { + if a == nil || maxDepth <= 0 { + return false + } + exceedsDepth := false + visitor := NewExprVisitor(func(e Expr) { + if nav, ok := e.(NavigableExpr); ok && nav.Depth() >= maxDepth { + exceedsDepth = true + } + }) + // Bound the walk to maxDepth+1 levels so it never recurses past the first level that exceeds + // the limit, keeping the check itself safe on the deep inputs it guards against. + visit(NavigateAST(a), visitor, postOrder, 0, maxDepth+1) + return exceedsDepth +} + type visitOrder int const ( diff --git a/vendor/github.com/google/cel-go/common/containers/container.go b/vendor/github.com/google/cel-go/common/containers/container.go index fc146b6fc..fcfcdfc3f 100644 --- a/vendor/github.com/google/cel-go/common/containers/container.go +++ b/vendor/github.com/google/cel-go/common/containers/container.go @@ -227,7 +227,7 @@ func Abbrevs(qualifiedNames ...string) ContainerOption { } alias := qn[ind+1:] var err error - c, err = aliasAs("abbreviation", qn, alias)(c) + c, err = aliasAs("abbreviation", qn, alias, true)(c) if err != nil { return nil, err } @@ -236,31 +236,32 @@ func Abbrevs(qualifiedNames ...string) ContainerOption { } } -// Alias associates a fully-qualified name with a user-defined alias. +// Alias associates a name with a user-defined alias. // // In general, Abbrevs is preferred to Alias since the names generated from the Abbrevs option // are more easily traced back to source code. The Alias option is useful for propagating alias // configuration from one Container instance to another, and may also be useful for remapping // poorly chosen protobuf message / package names. -// -// Note: all of the rules that apply to Abbrevs also apply to Alias. func Alias(qualifiedName, alias string) ContainerOption { - return aliasAs("alias", qualifiedName, alias) + return aliasAs("alias", qualifiedName, alias, false) } -func aliasAs(kind, qualifiedName, alias string) ContainerOption { +func aliasAs(kind, qualifiedName, alias string, requireQualified bool) ContainerOption { return func(c *Container) (*Container, error) { if len(alias) == 0 || strings.Contains(alias, ".") { return nil, fmt.Errorf( "%s must be non-empty and simple (not qualified): %s=%s", kind, kind, alias) } + if len(qualifiedName) == 0 { + return nil, fmt.Errorf("%s must refer to a valid name: %s", kind, qualifiedName) + } if qualifiedName[0:1] == "." { return nil, fmt.Errorf("qualified name must not begin with a leading '.': %s", qualifiedName) } ind := strings.LastIndex(qualifiedName, ".") - if ind <= 0 || ind == len(qualifiedName)-1 { + if ind == len(qualifiedName)-1 || (requireQualified && ind <= 0) { return nil, fmt.Errorf("%s must refer to a valid qualified name: %s", kind, qualifiedName) } diff --git a/vendor/github.com/google/cel-go/common/debug/debug.go b/vendor/github.com/google/cel-go/common/debug/debug.go index 75f5f0d63..fbc847f0c 100644 --- a/vendor/github.com/google/cel-go/common/debug/debug.go +++ b/vendor/github.com/google/cel-go/common/debug/debug.go @@ -312,3 +312,18 @@ func (w *debugWriter) removeIndent() { func (w *debugWriter) String() string { return w.buffer.String() } + +type idAdorner struct{} + +func (a *idAdorner) GetMetadata(elem any) string { + e, isExpr := elem.(ast.Expr) + if !isExpr { + return "" + } + return fmt.Sprintf("@id:%d ", e.ID()) +} + +// ToDebugStringWithIDs returns a string representation with AST node IDs. +func ToDebugStringWithIDs(e ast.Expr) string { + return ToAdornedDebugString(e, &idAdorner{}) +} diff --git a/vendor/github.com/google/cel-go/common/decls/decls.go b/vendor/github.com/google/cel-go/common/decls/decls.go index a4a51c3f2..5c50b8e99 100644 --- a/vendor/github.com/google/cel-go/common/decls/decls.go +++ b/vendor/github.com/google/cel-go/common/decls/decls.go @@ -16,6 +16,7 @@ package decls import ( + "context" "fmt" "strings" @@ -77,6 +78,9 @@ type FunctionDecl struct { // overloadOrdinals indicates the order in which the overload was declared. overloadOrdinals []string + + // overloadDecls caches the slice of overloads in declaration order. + overloadDecls []*OverloadDecl } type declarationState int @@ -150,7 +154,8 @@ func (f *FunctionDecl) Merge(other *FunctionDecl) (*FunctionDecl, error) { name: f.Name(), overloads: make(map[string]*OverloadDecl, len(f.overloads)), singleton: f.singleton, - overloadOrdinals: make([]string, len(f.overloads)), + overloadOrdinals: make([]string, len(f.overloadOrdinals)), + overloadDecls: make([]*OverloadDecl, len(f.overloadDecls)), // if one function is expecting type-guards and the other is not, then they // must not be disabled. disableTypeGuards: f.disableTypeGuards && other.disableTypeGuards, @@ -169,6 +174,7 @@ func (f *FunctionDecl) Merge(other *FunctionDecl) (*FunctionDecl, error) { } // baseline copy of the overloads and their ordinals copy(merged.overloadOrdinals, f.overloadOrdinals) + copy(merged.overloadDecls, f.overloadDecls) for oID, o := range f.overloads { merged.overloads[oID] = o } @@ -231,11 +237,13 @@ func (f *FunctionDecl) Subset(selector OverloadSelector) *FunctionDecl { } overloads := make(map[string]*OverloadDecl) overloadOrdinals := make([]string, 0, len(f.overloadOrdinals)) + overloadDecls := make([]*OverloadDecl, 0, len(f.overloadDecls)) for _, oID := range f.overloadOrdinals { overload := f.overloads[oID] if selector(overload) { overloads[oID] = overload overloadOrdinals = append(overloadOrdinals, oID) + overloadDecls = append(overloadDecls, overload) } } if len(overloads) == 0 { @@ -249,6 +257,7 @@ func (f *FunctionDecl) Subset(selector OverloadSelector) *FunctionDecl { disableTypeGuards: f.disableTypeGuards, state: f.state, overloadOrdinals: overloadOrdinals, + overloadDecls: overloadDecls, } return subset } @@ -270,8 +279,14 @@ func (f *FunctionDecl) AddOverload(overload *OverloadDecl) error { if oID == overload.ID() { if o.SignatureEquals(overload) && o.IsNonStrict() == overload.IsNonStrict() { // Allow redefinition of an overload implementation so long as the signatures match. - if overload.hasBinding() { + if overload.HasBinding() { f.overloads[oID] = overload + for i, decl := range f.overloadDecls { + if decl.ID() == oID { + f.overloadDecls[i] = overload + break + } + } } // Allow redefinition of the doc string. if len(overload.doc) != 0 && o.doc != overload.doc { @@ -287,20 +302,24 @@ func (f *FunctionDecl) AddOverload(overload *OverloadDecl) error { } f.overloadOrdinals = append(f.overloadOrdinals, overload.ID()) f.overloads[overload.ID()] = overload + f.overloadDecls = append(f.overloadDecls, overload) return nil } // OverloadDecls returns the overload declarations in the order in which they were declared. func (f *FunctionDecl) OverloadDecls() []*OverloadDecl { - var emptySet []*OverloadDecl if f == nil { - return emptySet + return nil } - overloads := make([]*OverloadDecl, 0, len(f.overloads)) - for _, oID := range f.overloadOrdinals { - overloads = append(overloads, f.overloads[oID]) + return f.overloadDecls +} + +// HasSingletonBinding indicates whether the function has a singleton binding definition. +func (f *FunctionDecl) HasSingletonBinding() bool { + if f == nil { + return false } - return overloads + return f.singleton != nil } // HasLateBinding returns true if the function has late bindings. A function cannot mix late bindings with other bindings. @@ -308,8 +327,12 @@ func (f *FunctionDecl) HasLateBinding() bool { if f == nil { return false } + if f.singleton != nil && f.singleton.Async != nil { + return true + } for _, oID := range f.overloadOrdinals { - if f.overloads[oID].HasLateBinding() { + o := f.overloads[oID] + if o.HasLateBinding() { return true } } @@ -328,12 +351,13 @@ func (f *FunctionDecl) Bindings() ([]*functions.Overload, error) { for _, oID := range f.overloadOrdinals { o := f.overloads[oID] hasLateBinding = hasLateBinding || o.HasLateBinding() - if o.hasBinding() { + if o.HasBinding() { overload := &functions.Overload{ Operator: o.ID(), Unary: o.guardedUnaryOp(f.Name(), f.disableTypeGuards), Binary: o.guardedBinaryOp(f.Name(), f.disableTypeGuards), Function: o.guardedFunctionOp(f.Name(), f.disableTypeGuards), + Async: o.guardedAsyncOp(f.Name(), f.disableTypeGuards), OperandTrait: o.OperandTrait(), NonStrict: o.IsNonStrict(), } @@ -354,6 +378,7 @@ func (f *FunctionDecl) Bindings() ([]*functions.Overload, error) { Unary: f.singleton.Unary, Binary: f.singleton.Binary, Function: f.singleton.Function, + Async: f.singleton.Async, OperandTrait: f.singleton.OperandTrait, }, } @@ -372,6 +397,7 @@ func (f *FunctionDecl) Bindings() ([]*functions.Overload, error) { Unary: overloads[0].Unary, Binary: overloads[0].Binary, Function: overloads[0].Function, + Async: overloads[0].Async, NonStrict: overloads[0].NonStrict, OperandTrait: overloads[0].OperandTrait, }), nil @@ -530,6 +556,30 @@ func SingletonFunctionBinding(fn functions.FunctionOp, traits ...int) FunctionOp } } +// SingletonAsyncBinding creates a singleton async function definition to be used with all function overloads. +// The provided function is called in its own goroutine with the provided context. The function should +// block until the result is available, and the framework manages goroutine and channel lifecycle. +// +// Note, this approach works well if operand is expected to have a specific trait which it implements, +// e.g. traits.ContainerType. Otherwise, prefer per-overload async bindings. +func SingletonAsyncBinding(fn functions.BlockingAsyncOp, traits ...int) FunctionOpt { + trait := 0 + for _, t := range traits { + trait = trait | t + } + return func(f *FunctionDecl) (*FunctionDecl, error) { + if f.singleton != nil { + return nil, fmt.Errorf("function already has a singleton binding: %s", f.Name()) + } + f.singleton = &functions.Overload{ + Operator: f.Name(), + Async: wrapAsyncOp(fn), + OperandTrait: trait, + } + return f, nil + } +} + // Overload defines a new global overload with an overload id, argument types, and result type. Through the // use of OverloadOpt options, the overload may also be configured with a binding, an operand trait, and to // be non-strict. @@ -614,6 +664,8 @@ type OverloadDecl struct { binaryOp functions.BinaryOp // functionOp is a catch-all for zero-arity and three-plus arity functions. functionOp functions.FunctionOp + // asyncOp is an asynchronous function binding that returns a channel. + asyncOp functions.AsyncOp } // Examples returns a list of string examples for the overload. @@ -669,7 +721,7 @@ func (o *OverloadDecl) HasLateBinding() bool { if o == nil { return false } - return o.hasLateBinding + return o.hasLateBinding || o.asyncOp != nil } // OperandTrait returns the trait mask of the first operand to the overload call, e.g. @@ -740,9 +792,9 @@ func (o *OverloadDecl) SignatureOverlaps(other *OverloadDecl) bool { return argsOverlap } -// hasBinding indicates whether the overload already has a definition. -func (o *OverloadDecl) hasBinding() bool { - return o != nil && (o.unaryOp != nil || o.binaryOp != nil || o.functionOp != nil) +// HasBinding indicates whether the overload already has a definition. +func (o *OverloadDecl) HasBinding() bool { + return o != nil && (o.unaryOp != nil || o.binaryOp != nil || o.functionOp != nil || o.asyncOp != nil) } // guardedUnaryOp creates an invocation guard around the provided unary operator, if one is defined. @@ -784,6 +836,22 @@ func (o *OverloadDecl) guardedFunctionOp(funcName string, disableTypeGuards bool } } +// guardedAsyncOp creates an invocation guard around the provided async function binding, if one is provided. +func (o *OverloadDecl) guardedAsyncOp(funcName string, disableTypeGuards bool) functions.AsyncOp { + if o.asyncOp == nil { + return nil + } + return func(ctx context.Context, args ...ref.Val) <-chan ref.Val { + if !o.matchesRuntimeSignature(disableTypeGuards, args...) { + ch := make(chan ref.Val, 1) + ch <- MaybeNoSuchOverload(funcName, args...) + close(ch) + return ch + } + return o.asyncOp(ctx, args...) + } +} + // matchesRuntimeUnarySignature indicates whether the argument type is runtime assiganble to the overload's expected argument. func (o *OverloadDecl) matchesRuntimeUnarySignature(disableTypeGuards bool, arg ref.Val) bool { return matchRuntimeArgType(o.IsNonStrict(), disableTypeGuards, o.ArgTypes()[0], arg) && @@ -817,6 +885,8 @@ func matchRuntimeArgType(nonStrict, disableTypeGuards bool, argType *types.Type, if nonStrict && (disableTypeGuards || types.IsUnknownOrError(arg)) { return true } + // Note, early returns and unknown aggregation happen in the interpretable.go file; however, this check is here + // for defense in depth or for scenarios where someone manipulates bindings to offer their own dispatch logic. if types.IsUnknownOrError(arg) { return false } @@ -842,7 +912,7 @@ func OverloadExamples(examples ...string) OverloadOpt { // type-guard which ensures runtime type agreement between the overload signature and runtime argument types. func UnaryBinding(binding functions.UnaryOp) OverloadOpt { return func(o *OverloadDecl) (*OverloadDecl, error) { - if o.hasBinding() { + if o.HasBinding() { return nil, fmt.Errorf("overload already has a binding: %s", o.ID()) } if len(o.ArgTypes()) != 1 { @@ -860,7 +930,7 @@ func UnaryBinding(binding functions.UnaryOp) OverloadOpt { // type-guard which ensures runtime type agreement between the overload signature and runtime argument types. func BinaryBinding(binding functions.BinaryOp) OverloadOpt { return func(o *OverloadDecl) (*OverloadDecl, error) { - if o.hasBinding() { + if o.HasBinding() { return nil, fmt.Errorf("overload already has a binding: %s", o.ID()) } if len(o.ArgTypes()) != 2 { @@ -878,7 +948,7 @@ func BinaryBinding(binding functions.BinaryOp) OverloadOpt { // type-guard which ensures runtime type agreement between the overload signature and runtime argument types. func FunctionBinding(binding functions.FunctionOp) OverloadOpt { return func(o *OverloadDecl) (*OverloadDecl, error) { - if o.hasBinding() { + if o.HasBinding() { return nil, fmt.Errorf("overload already has a binding: %s", o.ID()) } if o.hasLateBinding { @@ -889,11 +959,45 @@ func FunctionBinding(binding functions.FunctionOp) OverloadOpt { } } +// AsyncBinding provides the implementation of an asynchronous overload. The provided function +// is called in its own goroutine with the provided context. The function should block until +// the result is available, and the framework manages goroutine and channel lifecycle. +// +// This follows the same pattern used by gRPC-Go and other major Go frameworks where user +// code is synchronous and the framework manages concurrency. +func AsyncBinding(fn functions.BlockingAsyncOp) OverloadOpt { + return func(o *OverloadDecl) (*OverloadDecl, error) { + if o.HasBinding() { + return nil, fmt.Errorf("overload already has a binding: %s", o.ID()) + } + if o.hasLateBinding { + return nil, fmt.Errorf("overload already has a late binding: %s", o.ID()) + } + o.asyncOp = wrapAsyncOp(fn) + return o, nil + } +} + +// wrapAsyncOp adapts a blocking function into the channel-based AsyncOp used internally. +// +// The blocking function is invoked synchronously and its result delivered on a buffered channel. +// The interpreter always invokes an AsyncOp from a dedicated goroutine, so running the blocking +// call inline here keeps the framework to a single goroutine per async call rather than spawning +// an additional one to bridge blocking-to-channel. +func wrapAsyncOp(fn functions.BlockingAsyncOp) functions.AsyncOp { + return func(ctx context.Context, args ...ref.Val) <-chan ref.Val { + ch := make(chan ref.Val, 1) + ch <- fn(ctx, args...) + close(ch) + return ch + } +} + // LateFunctionBinding indicates that the function has a binding which is not known at compile time. // This is useful for functions which have side-effects or are not deterministically computable. func LateFunctionBinding() OverloadOpt { return func(o *OverloadDecl) (*OverloadDecl, error) { - if o.hasBinding() { + if o.HasBinding() { return nil, fmt.Errorf("overload already has a binding: %s", o.ID()) } o.hasLateBinding = true diff --git a/vendor/github.com/google/cel-go/common/doc.go b/vendor/github.com/google/cel-go/common/doc.go index 06eae3642..c10742c28 100644 --- a/vendor/github.com/google/cel-go/common/doc.go +++ b/vendor/github.com/google/cel-go/common/doc.go @@ -37,6 +37,8 @@ const ( DocMacro // DocExample represents example documentation. DocExample + // DocField represents documentation for a struct field. + DocField ) // Doc holds the documentation details for a specific program element like @@ -163,6 +165,17 @@ func NewExampleDoc(ex string) *Doc { } } +// NewFieldDoc creates a new Doc struct for documenting a struct field. +func NewFieldDoc(name, celType, description string, examples ...*Doc) *Doc { + return &Doc{ + Kind: DocField, + Name: name, + Type: celType, + Description: description, + Children: examples, + } +} + // Documentor is an interface for types that can provide their own documentation. type Documentor interface { // Documentation returns the documentation coded by the DocKind to assist diff --git a/vendor/github.com/google/cel-go/common/env/BUILD.bazel b/vendor/github.com/google/cel-go/common/env/BUILD.bazel index aebe1e544..261da924d 100644 --- a/vendor/github.com/google/cel-go/common/env/BUILD.bazel +++ b/vendor/github.com/google/cel-go/common/env/BUILD.bazel @@ -23,12 +23,14 @@ go_library( name = "go_default_library", srcs = [ "env.go", + "io.go", ], importpath = "github.com/google/cel-go/common/env", deps = [ "//common:go_default_library", "//common/decls:go_default_library", "//common/types:go_default_library", + "@in_yaml_go_yaml_v3//:go_default_library", ], ) @@ -37,6 +39,7 @@ go_test( size = "small", srcs = [ "env_test.go", + "io_test.go", ], data = glob(["testdata/**"]), embed = [":go_default_library"], @@ -45,6 +48,7 @@ go_test( "//common/operators:go_default_library", "//common/overloads:go_default_library", "//common/types:go_default_library", - "@in_gopkg_yaml_v3//:go_default_library", + "@com_github_google_go_cmp//cmp:go_default_library", + "@in_yaml_go_yaml_v3//:go_default_library", ], ) diff --git a/vendor/github.com/google/cel-go/common/env/env.go b/vendor/github.com/google/cel-go/common/env/env.go index d848860c2..936036ed2 100644 --- a/vendor/github.com/google/cel-go/common/env/env.go +++ b/vendor/github.com/google/cel-go/common/env/env.go @@ -50,6 +50,7 @@ type Config struct { Functions []*Function `yaml:"functions,omitempty"` Validators []*Validator `yaml:"validators,omitempty"` Features []*Feature `yaml:"features,omitempty"` + Limits []*Limit `yaml:"limits,omitempty"` } // Validate validates the whole configuration is well-formed. @@ -92,6 +93,11 @@ func (c *Config) Validate() error { errs = append(errs, err) } } + for _, limit := range c.Limits { + if err := limit.Validate(); err != nil { + errs = append(errs, err) + } + } for _, val := range c.Validators { if err := val.Validate(); err != nil { errs = append(errs, err) @@ -122,7 +128,7 @@ func (c *Config) AddVariableDecls(vars ...*decls.VariableDecl) *Config { return c.AddVariables(convVars...) } -// AddVariables adds one or more vairables to the config. +// AddVariables adds one or more variables to the config. func (c *Config) AddVariables(vars ...*Variable) *Config { c.Variables = append(c.Variables, vars...) return c @@ -206,6 +212,12 @@ func (c *Config) AddFeatures(feats ...*Feature) *Config { return c } +// AddLimits appends one or more limits to the config. +func (c *Config) AddLimits(limits ...*Limit) *Config { + c.Limits = append(c.Limits, limits...) + return c +} + // NewImport returns a serializable import value from the qualified type name. func NewImport(name string) *Import { return &Import{Name: name} @@ -246,7 +258,9 @@ type Variable struct { // Type represents the type declaration for the variable. // - // Deprecated: use the embedded *TypeDesc fields directly. + // When serialized, 'type' is used for shorthand specifier string. + // + // Use GetType() for getting the effective type. Type *TypeDesc `yaml:"type,omitempty"` // TypeDesc is an embedded set of fields allowing for the specification of the Variable type. @@ -264,6 +278,9 @@ func (v *Variable) Validate() error { if err := v.GetType().Validate(); err != nil { return fmt.Errorf("invalid variable %q: %w", v.Name, err) } + if v.GetType().IsTypeParam { + return fmt.Errorf("invalid variable %q: variables cannot be type parameters", v.Name) + } return nil } @@ -734,6 +751,29 @@ func (feat *Feature) Validate() error { return nil } +// Limit represents a named limit in the CEL environment. This is used to control +// the complexity tolerated before failing parsing, type checking, or planning. +type Limit struct { + Name string `yaml:"name"` + Value int `yaml:"value"` +} + +// NewLimit creates a new limit. +func NewLimit(name string, value int) *Limit { + return &Limit{name, value} +} + +// Validate validates a limit. +func (l *Limit) Validate() error { + if l == nil { + return errors.New("invalid limit: nil") + } + if l.Name == "" { + return errors.New("invalid limit: missing name") + } + return nil +} + // NewTypeDesc describes a simple or complex type with parameters. func NewTypeDesc(typeName string, params ...*TypeDesc) *TypeDesc { return &TypeDesc{TypeName: typeName, Params: params} @@ -796,11 +836,47 @@ func (td *TypeDesc) Validate() error { return fmt.Errorf("invalid type: optional_type expects 1 parameter, got %d", len(td.Params)) } return td.Params[0].Validate() + case "type": + if len(td.Params) == 0 { + return nil + } + if len(td.Params) != 1 { + return fmt.Errorf("invalid type: type expects 0 or 1 parameters, got %d", len(td.Params)) + } + return td.Params[0].Validate() default: } return nil } +func formatSpecifierImpl(td *TypeDesc, sb *strings.Builder) { + if td.IsTypeParam { + sb.WriteRune('~') + sb.WriteString(td.TypeName) + return + } + sb.WriteString(td.TypeName) + l := len(td.Params) + if l < 1 { + return + } + sb.WriteRune('<') + for i, p := range td.Params { + formatSpecifierImpl(p, sb) + if i < l-1 { + sb.WriteString(", ") + } + } + sb.WriteRune('>') +} + +// SpecifierFormat returns the short text representation of the type. e.g. "map" +func (td *TypeDesc) SpecifierFormat() string { + var sb strings.Builder + formatSpecifierImpl(td, &sb) + return sb.String() +} + // AsCELType converts the serializable object to a *types.Type value. func (td *TypeDesc) AsCELType(tp types.Provider) (*types.Type, error) { err := td.Validate() @@ -810,6 +886,27 @@ func (td *TypeDesc) AsCELType(tp types.Provider) (*types.Type, error) { switch td.TypeName { case "dyn": return types.DynType, nil + // short aliases for WKTs + case "duration": + return types.DurationType, nil + case "timestamp": + return types.TimestampType, nil + case "any": + return types.AnyType, nil + case "null", "null_type": + return types.NullType, nil + case "bool_wrapper": + return types.NewNullableType(types.BoolType), nil + case "bytes_wrapper": + return types.NewNullableType(types.BytesType), nil + case "double_wrapper": + return types.NewNullableType(types.DoubleType), nil + case "int_wrapper": + return types.NewNullableType(types.IntType), nil + case "uint_wrapper": + return types.NewNullableType(types.UintType), nil + case "string_wrapper": + return types.NewNullableType(types.StringType), nil case "map": kt, err := td.Params[0].AsCELType(tp) if err != nil { @@ -832,6 +929,15 @@ func (td *TypeDesc) AsCELType(tp types.Provider) (*types.Type, error) { return nil, err } return types.NewOptionalType(et), nil + case "type": + if len(td.Params) == 0 { + return types.TypeType, nil + } + pt, err := td.Params[0].AsCELType(tp) + if err != nil { + return nil, err + } + return types.NewTypeTypeWithParam(pt), nil default: if td.IsTypeParam { return types.NewTypeParamType(td.TypeName), nil @@ -874,6 +980,15 @@ func SerializeTypeDesc(t *types.Type) *TypeDesc { for _, p := range t.Parameters() { params = append(params, SerializeTypeDesc(p)) } + // Special types, these aren't useful for describing environments. + switch t.Kind() { + case types.ErrorKind: + typeName = "*error*" + case types.UnknownKind: + typeName = "*unknown*" + case types.UnspecifiedKind: + typeName = "*unspecified type*" + } return NewTypeDesc(typeName, params...) } diff --git a/vendor/github.com/google/cel-go/common/env/io.go b/vendor/github.com/google/cel-go/common/env/io.go new file mode 100644 index 000000000..ec126f9ce --- /dev/null +++ b/vendor/github.com/google/cel-go/common/env/io.go @@ -0,0 +1,271 @@ +// Copyright 2026 Google LLC +// +// 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 env + +import ( + "errors" + "fmt" + + "go.yaml.in/yaml/v3" +) + +type internalTypeDesc struct { + TypeName string `yaml:"type_name"` + Params []*TypeDesc `yaml:"params,omitempty"` + IsTypeParam bool `yaml:"is_type_param,omitempty"` +} + +// Embedding TypeDesc in variable causes issues with customizing +// unmarshalling / marshalling. Work around with a parallel type. +type internalVariable struct { + Name string `yaml:"name"` + Description string `yaml:"description,omitempty"` + + // Type represents the type declaration for the variable. + Type *TypeDesc `yaml:"type,omitempty"` + + TypeName string `yaml:"type_name"` + Params []*TypeDesc `yaml:"params,omitempty"` + IsTypeParam bool `yaml:"is_type_param,omitempty"` +} + +// UnmarshalYAML implements yaml.Unmarshal +func (v *Variable) UnmarshalYAML(n *yaml.Node) error { + buf := internalVariable{} + err := n.Decode(&buf) + if err != nil { + return err + } + v.Name = buf.Name + v.Description = buf.Description + if buf.TypeName != "" { + v.TypeDesc = &TypeDesc{ + TypeName: buf.TypeName, + Params: buf.Params, + IsTypeParam: buf.IsTypeParam, + } + } else if buf.Type != nil { + v.TypeDesc = buf.Type + } + return nil +} + +// MarshalYAML implements yaml.Marshaler +func (v *Variable) MarshalYAML() (any, error) { + // The presence of an unmarshaller alters the default marshaller behavior so + // provide a simple marshal implementation. + buf := internalVariable{ + Name: v.Name, + Description: v.Description, + } + if t := v.GetType(); t != nil { + buf.TypeName = t.TypeName + buf.Params = t.Params + buf.IsTypeParam = t.IsTypeParam + } + return &buf, nil +} + +// UnmarshalYAML implements yaml.Unmarshaler +func (td *TypeDesc) UnmarshalYAML(n *yaml.Node) error { + if td == nil { + return fmt.Errorf("unexpected Unmarshal for TypeDesc at: %d", n.Line) + } + if n.Kind == yaml.ScalarNode { + o, err := ParseTypeDesc(n.Value) + if err != nil { + return err + } + *td = *o + return nil + } + + if n.Kind != yaml.MappingNode { + return errors.New("unsupported yaml for TypeDesc") + } + + buf := internalTypeDesc{} + err := n.Decode(&buf) + if err != nil { + return err + } + td.TypeName = buf.TypeName + td.Params = buf.Params + td.IsTypeParam = buf.IsTypeParam + return nil +} + +type typeDescParser struct { + text string + pos int + length int +} + +// ParseTypeDesc parses a TypeDesc from the type specifier format: "map" +func ParseTypeDesc(text string) (*TypeDesc, error) { + p := &typeDescParser{text: text, length: len(text)} + res, err := p.parseTypeElem() + if err != nil { + return nil, fmt.Errorf("failed to parse type %q: %v", text, err) + } + p.skipWhitespace() + if p.pos < p.length { + return nil, fmt.Errorf("unexpected character %q at position %d in %q", p.text[p.pos], p.pos, text) + } + return res, nil +} + +func (p *typeDescParser) parseConcreteType() (*TypeDesc, error) { + id, err := p.parseNamespaceIdentifier() + if err != nil { + return nil, err + } + if p.pos < p.length && p.text[p.pos] == '<' { + p.pos++ // consume '<' + var params []*TypeDesc + for { + p.skipWhitespace() + param, err := p.parseTypeElem() + if err != nil { + return nil, err + } + params = append(params, param) + p.skipWhitespace() + if p.pos < p.length && p.text[p.pos] == ',' { + p.pos++ // consume ',' + continue + } + if p.pos < p.length && p.text[p.pos] == '>' { + p.pos++ // consume '>' + break + } + return nil, fmt.Errorf("expected ',' or '>' at position %d", p.pos) + } + return NewTypeDesc(id, params...), nil + } + return NewTypeDesc(id), nil +} + +func (p *typeDescParser) parseTypeElem() (*TypeDesc, error) { + p.skipWhitespace() + if p.pos < p.length && p.text[p.pos] == '~' { + p.pos++ // consume '~' + id, err := p.parseTypeParamIdent() + if err != nil { + return nil, err + } + return NewTypeParam(id), nil + } + return p.parseConcreteType() +} + +func (p *typeDescParser) parseNamespaceIdentifier() (string, error) { + p.skipWhitespace() + var id string + for p.pos < p.length && p.text[p.pos] != '<' { + c := p.text[p.pos] + if c == '.' { + id += "." + p.pos++ // consume '.' + } + ident, err := p.parseIdentifier() + if err != nil { + return "", err + } + id += ident + p.skipWhitespace() + if p.pos < p.length && p.text[p.pos] != '.' { + break + } + } + if id == "" { + return "", fmt.Errorf("missing identifier at position %d", p.pos) + } + return id, nil +} + +func (p *typeDescParser) parseIdentifier() (string, error) { + p.skipWhitespace() + if p.pos >= p.length { + return "", fmt.Errorf("unexpected end of input") + } + start := p.pos + c := p.text[p.pos] + if !isAlpha(c) && c != '_' { + return "", fmt.Errorf("identifier is expected, but %q was found at position %d", c, p.pos) + } + p.pos++ + for p.pos < p.length { + c := p.text[p.pos] + if !isAlphaNumeric(c) && c != '_' { + break + } + p.pos++ + } + return p.text[start:p.pos], nil +} + +func (p *typeDescParser) parseTypeParamIdent() (string, error) { + p.skipWhitespace() + if p.pos >= p.length { + return "", fmt.Errorf("unexpected end of input") + } + c := p.text[p.pos] + if !isAlpha(c) { + return "", fmt.Errorf("invalid type parameter identifier %q at position %d, must be a single character from A-Z", c, p.pos) + } + p.pos++ + if p.pos < p.length && isAlpha(p.text[p.pos]) { + return "", fmt.Errorf("invalid type param, must have a single alphabetic character at position %d", p.pos) + } + return string(c), nil +} + +func (p *typeDescParser) skipWhitespace() { + for p.pos < p.length && p.text[p.pos] == ' ' { + p.pos++ + } +} + +func isAlpha(c byte) bool { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') +} + +func isAlphaNumeric(c byte) bool { + return isAlpha(c) || (c >= '0' && c <= '9') +} + +// ConfigFromYAML returns a config from YAML source. +// +// Adds custom parsing logic for normalizing shorthand for specifiying some fields +// in a YAML document (mainly the type-specifier shorthand). +// +// Using yaml.Unmarshal with any implementation should be sufficient for most +// cases. +func ConfigFromYAML(data []byte) (*Config, error) { + c := &Config{} + e := yaml.Unmarshal(data, c) + if e != nil { + return nil, e + } + return c, nil +} + +// ConfigToYAML returns the config serialized to YAML +// +// Provided as a convenience wrapper around a tested YAML Marshaler. +func ConfigToYAML(c *Config) ([]byte, error) { + return yaml.Marshal(c) +} diff --git a/vendor/github.com/google/cel-go/common/functions/functions.go b/vendor/github.com/google/cel-go/common/functions/functions.go index 67f4a5944..0c00781d9 100644 --- a/vendor/github.com/google/cel-go/common/functions/functions.go +++ b/vendor/github.com/google/cel-go/common/functions/functions.go @@ -15,7 +15,11 @@ // Package functions defines the standard builtin functions supported by the interpreter package functions -import "github.com/google/cel-go/common/types/ref" +import ( + "context" + + "github.com/google/cel-go/common/types/ref" +) // Overload defines a named overload of a function, indicating an operand trait // which must be present on the first argument to the overload as well as one @@ -41,21 +45,37 @@ type Overload struct { // Binary defines the overload with a BinaryOp implementation. May be nil. Binary BinaryOp - // Function defines the overload with a FunctionOp implementation. May be - // nil. + // Function defines the overload with a FunctionOp implementation. May be nil. Function FunctionOp + // Async defines the overload with an AsyncOp implementation. May be nil. + Async AsyncOp + // NonStrict specifies whether the Overload will tolerate arguments that // are types.Err or types.Unknown. NonStrict bool } // UnaryOp is a function that takes a single value and produces an output. -type UnaryOp func(value ref.Val) ref.Val +type UnaryOp func(ref.Val) ref.Val // BinaryOp is a function that takes two values and produces an output. -type BinaryOp func(lhs ref.Val, rhs ref.Val) ref.Val +type BinaryOp func(ref.Val, ref.Val) ref.Val // FunctionOp is a function with accepts zero or more arguments and produces // a value or error as a result. -type FunctionOp func(values ...ref.Val) ref.Val +type FunctionOp func(...ref.Val) ref.Val + +// AsyncOp is a function that accepts zero or more arguments and produces +// a value or error asynchronously via a channel. +// +// AsyncOp is an internal interface intended for use by CEL to manage goroutines and +// channels associated with async calls. For public API usage, use BlockingAsyncOp. +// Implementers should listen for context cancellation on the provided context for +// resource cleanup. +type AsyncOp func(context.Context, ...ref.Val) <-chan ref.Val + +// BlockingAsyncOp is a function that accepts zero or more arguments and blocks until +// the result is available. When used with AsyncBinding, the framework runs the function +// in its own goroutine and manages channel lifecycle internally. +type BlockingAsyncOp func(context.Context, ...ref.Val) ref.Val diff --git a/vendor/github.com/google/cel-go/common/overloads/overloads.go b/vendor/github.com/google/cel-go/common/overloads/overloads.go index 9d50f4367..0e3a72449 100644 --- a/vendor/github.com/google/cel-go/common/overloads/overloads.go +++ b/vendor/github.com/google/cel-go/common/overloads/overloads.go @@ -291,7 +291,6 @@ const ( const ( DurationToDuration = "duration_to_duration" StringToDuration = "string_to_duration" - IntToDuration = "int64_to_duration" ) // Convert to dyn diff --git a/vendor/github.com/google/cel-go/common/runes/buffer.go b/vendor/github.com/google/cel-go/common/runes/buffer.go index 021198224..58dd33e13 100644 --- a/vendor/github.com/google/cel-go/common/runes/buffer.go +++ b/vendor/github.com/google/cel-go/common/runes/buffer.go @@ -16,6 +16,7 @@ package runes import ( + "fmt" "strings" "unicode/utf8" ) @@ -113,45 +114,64 @@ var _ Buffer = &supplementalBuffer{} var nilBuffer = &emptyBuffer{} +// SizeLimitError indicates that the input exceeded the configured code point limit. +type SizeLimitError struct { + Size int + Limit int +} + +func (e *SizeLimitError) Error() string { + return fmt.Sprintf("expression code point size exceeds limit: size: %d, limit %d", e.Size, e.Limit) +} + // NewBuffer returns an efficient implementation of Buffer for the given text based on the ranges of // the encoded code points contained within. -// -// Code points are represented as an array of byte, uint16, or rune. This approach ensures that -// each index represents a code point by itself without needing to use an array of rune. At first -// we assume all code points are less than or equal to '\u007f'. If this holds true, the -// underlying storage is a byte array containing only ASCII characters. If we encountered a code -// point above this range but less than or equal to '\uffff' we allocate a uint16 array, copy the -// elements of previous byte array to the uint16 array, and continue. If this holds true, the -// underlying storage is a uint16 array containing only Unicode characters in the Basic Multilingual -// Plane. If we encounter a code point above '\uffff' we allocate an rune array, copy the previous -// elements of the byte or uint16 array, and continue. The underlying storage is an rune array -// containing any Unicode character. func NewBuffer(data string) Buffer { - buf, _ := newBuffer(data, false) + buf, _, _ := newBufferWithLimit(data, false, -1) return buf } // NewBufferAndLineOffsets returns an efficient implementation of Buffer for the given text based on // the ranges of the encoded code points contained within, as well as returning the line offsets. -// -// Code points are represented as an array of byte, uint16, or rune. This approach ensures that -// each index represents a code point by itself without needing to use an array of rune. At first -// we assume all code points are less than or equal to '\u007f'. If this holds true, the -// underlying storage is a byte array containing only ASCII characters. If we encountered a code -// point above this range but less than or equal to '\uffff' we allocate a uint16 array, copy the -// elements of previous byte array to the uint16 array, and continue. If this holds true, the -// underlying storage is a uint16 array containing only Unicode characters in the Basic Multilingual -// Plane. If we encounter a code point above '\uffff' we allocate an rune array, copy the previous -// elements of the byte or uint16 array, and continue. The underlying storage is an rune array -// containing any Unicode character. func NewBufferAndLineOffsets(data string) (Buffer, []int32) { - return newBuffer(data, true) + buf, offs, _ := newBufferWithLimit(data, true, -1) + return buf, offs +} + +// NewBufferAndLineOffsetsWithLimit returns an efficient implementation of Buffer for the given text +// and enforces a code point limit while constructing the buffer. +func NewBufferAndLineOffsetsWithLimit(data string, limit int) (Buffer, []int32, error) { + if limit < 0 || len(data) <= limit { + return newBufferWithLimit(data, true, -1) + } + return newBufferWithLimit(data, true, limit) +} + +func countRemainingCodePoints(data string, idx int, count int) int { + for idx < len(data) { + _, s := utf8.DecodeRuneInString(data[idx:]) + idx += s + count++ + } + return count } -func newBuffer(data string, lines bool) (Buffer, []int32) { +func newBufferWithLimit(data string, lines bool, limit int) (Buffer, []int32, error) { if len(data) == 0 { - return nilBuffer, []int32{0} + return nilBuffer, []int32{0}, nil + } + if limit >= 0 && len(data) > limit { + size := countRemainingCodePoints(data, 0, 0) + if size > limit { + return nil, nil, &SizeLimitError{ + Size: size, + Limit: limit, + } + } } + + // The resulting buffers store one element per code point, so the worst case + // element count never exceeds len(data). var ( idx = 0 off int32 = 0 @@ -195,7 +215,8 @@ func newBuffer(data string, lines bool) (Buffer, []int32) { } return &asciiBuffer{ arr: buf8, - }, offs + }, offs, nil + copy16: for idx < len(data) { r, s := utf8.DecodeRuneInString(data[idx:]) @@ -222,7 +243,8 @@ copy16: } return &basicBuffer{ arr: buf16, - }, offs + }, offs, nil + copy32: for idx < len(data) { r, s := utf8.DecodeRuneInString(data[idx:]) @@ -238,5 +260,5 @@ copy32: } return &supplementalBuffer{ arr: buf32, - }, offs + }, offs, nil } diff --git a/vendor/github.com/google/cel-go/common/source.go b/vendor/github.com/google/cel-go/common/source.go index ec79cb545..9187e9b5c 100644 --- a/vendor/github.com/google/cel-go/common/source.go +++ b/vendor/github.com/google/cel-go/common/source.go @@ -74,6 +74,12 @@ func NewTextSource(text string) Source { return NewStringSource(text, "") } +// NewTextSourceWithLimit creates a new Source from the input text string while +// enforcing a maximum code point count when needed. +func NewTextSourceWithLimit(text string, limit int) (Source, error) { + return NewStringSourceWithLimit(text, "", limit) +} + // NewStringSource creates a new Source from the given contents and description. func NewStringSource(contents string, description string) Source { // Compute line offsets up front as they are referred to frequently. @@ -85,6 +91,23 @@ func NewStringSource(contents string, description string) Source { } } +// NewStringSourceWithLimit creates a new Source from the given contents and +// description while enforcing a maximum code point count when needed. +func NewStringSourceWithLimit(contents string, description string, limit int) (Source, error) { + if limit < 0 || len(contents) <= limit { + return NewStringSource(contents, description), nil + } + buf, offs, err := runes.NewBufferAndLineOffsetsWithLimit(contents, limit) + if err != nil { + return nil, err + } + return &sourceImpl{ + Buffer: buf, + description: description, + lineOffsets: offs, + }, nil +} + // NewInfoSource creates a new Source from a SourceInfo. func NewInfoSource(info *exprpb.SourceInfo) Source { return &sourceImpl{ diff --git a/vendor/github.com/google/cel-go/common/stdlib/standard.go b/vendor/github.com/google/cel-go/common/stdlib/standard.go index 4040a4f5c..3a151462e 100644 --- a/vendor/github.com/google/cel-go/common/stdlib/standard.go +++ b/vendor/github.com/google/cel-go/common/stdlib/standard.go @@ -16,6 +16,8 @@ package stdlib import ( + "fmt" + "math" "strconv" "strings" "time" @@ -310,6 +312,9 @@ func init() { argTypes(types.DurationType, types.DurationType), types.BoolType, decls.OverloadExamples(`duration('1ms') < duration('1s') // true`)), decls.SingletonBinaryBinding(func(lhs, rhs ref.Val) ref.Val { + if isNaN(lhs) || isNaN(rhs) { + return types.False + } cmp := lhs.(traits.Comparer).Compare(rhs) if cmp == types.IntNegOne { return types.True @@ -367,6 +372,9 @@ func init() { argTypes(types.DurationType, types.DurationType), types.BoolType, decls.OverloadExamples(`duration('1ms') <= duration('1s') // true`)), decls.SingletonBinaryBinding(func(lhs, rhs ref.Val) ref.Val { + if isNaN(lhs) || isNaN(rhs) { + return types.False + } cmp := lhs.(traits.Comparer).Compare(rhs) if cmp == types.IntNegOne || cmp == types.IntZero { return types.True @@ -424,6 +432,9 @@ func init() { argTypes(types.DurationType, types.DurationType), types.BoolType, decls.OverloadExamples(`duration('1ms') > duration('1us') // true`)), decls.SingletonBinaryBinding(func(lhs, rhs ref.Val) ref.Val { + if isNaN(lhs) || isNaN(rhs) { + return types.False + } cmp := lhs.(traits.Comparer).Compare(rhs) if cmp == types.IntOne { return types.True @@ -481,6 +492,9 @@ func init() { argTypes(types.DurationType, types.DurationType), types.BoolType, decls.OverloadExamples(`duration('60s') >= duration('1m') // true`)), decls.SingletonBinaryBinding(func(lhs, rhs ref.Val) ref.Val { + if isNaN(lhs) || isNaN(rhs) { + return types.False + } cmp := lhs.(traits.Comparer).Compare(rhs) if cmp == types.IntOne || cmp == types.IntZero { return types.True @@ -605,8 +619,6 @@ func init() { decls.Overload(overloads.DurationToDuration, argTypes(types.DurationType), types.DurationType, decls.OverloadExamples(`duration(duration('1s')) // duration('1s')`), decls.UnaryBinding(identity)), - decls.Overload(overloads.IntToDuration, argTypes(types.IntType), types.DurationType, - decls.UnaryBinding(convertToType(types.DurationType))), decls.Overload(overloads.StringToDuration, argTypes(types.StringType), types.DurationType, decls.OverloadExamples(`duration('1h2m3s') // duration('3723s')`), decls.UnaryBinding(convertToType(types.DurationType)))), @@ -928,6 +940,11 @@ func noBinaryOverrides(rhs, lhs ref.Val) ref.Val { return types.NoSuchOverloadErr() } +func isNaN(val ref.Val) bool { + d, ok := val.(types.Double) + return ok && math.IsNaN(float64(d)) +} + func noFunctionOverrides(args ...ref.Val) ref.Val { return types.NoSuchOverloadErr() } @@ -1037,7 +1054,7 @@ func inTimeZone(ts, tz ref.Val) (time.Time, error) { } // If the input is not the name of a timezone (for example, 'US/Central'), it should be a numerical offset from UTC - // in the format ^(+|-)(0[0-9]|1[0-4]):[0-5][0-9]$. The numerical input is parsed in terms of hours and minutes. + // in the format ^(+|-)([01]\d|2[0-3]):[0-5][0-9]$. The numerical input is parsed in terms of hours and minutes. hr, err := strconv.Atoi(string(val[0:ind])) if err != nil { return time.Time{}, err @@ -1046,6 +1063,12 @@ func inTimeZone(ts, tz ref.Val) (time.Time, error) { if err != nil { return time.Time{}, err } + if hr < -23 || hr > 23 { + return time.Time{}, fmt.Errorf("timezone offset hours out of range [-23, 23]: %s", val) + } + if min < 0 || min > 59 { + return time.Time{}, fmt.Errorf("timezone offset minutes out of range [0, 59]: %s", val) + } var offset int if string(val[0]) == "-" { offset = hr*60 - min diff --git a/vendor/github.com/google/cel-go/common/types/BUILD.bazel b/vendor/github.com/google/cel-go/common/types/BUILD.bazel index 7082bc755..4ecc40031 100644 --- a/vendor/github.com/google/cel-go/common/types/BUILD.bazel +++ b/vendor/github.com/google/cel-go/common/types/BUILD.bazel @@ -21,12 +21,15 @@ go_library( "format.go", "list.go", "map.go", + "native.go", "null.go", "object.go", "optional.go", "overflow.go", "provider.go", + "regex.go", "string.go", + "struct.go", "timestamp.go", "types.go", "uint.go", @@ -40,7 +43,6 @@ go_library( "//common/types/pb:go_default_library", "//common/types/ref:go_default_library", "//common/types/traits:go_default_library", - "@com_github_stoewer_go_strcase//:go_default_library", "@dev_cel_expr//:expr", "@org_golang_google_genproto_googleapis_api//expr/v1alpha1:go_default_library", "@org_golang_google_protobuf//encoding/protojson:go_default_library", @@ -68,10 +70,12 @@ go_test( "json_struct_test.go", "list_test.go", "map_test.go", + "native_test.go", "null_test.go", "object_test.go", "optional_test.go", "provider_test.go", + "regex_test.go", "string_test.go", "timestamp_test.go", "types_test.go", @@ -81,13 +85,19 @@ go_test( ], embed = [":go_default_library"], deps = [ + "//cel:go_default_library", + "//common/types/pb:go_default_library", "//common/types/ref:go_default_library", + "//common/types/traits:go_default_library", + "//ext:go_default_library", "//test:go_default_library", "//test/proto3pb:test_all_types_go_proto", "@org_golang_google_genproto_googleapis_api//expr/v1alpha1:go_default_library", "@org_golang_google_protobuf//encoding/protojson:go_default_library", + "@org_golang_google_protobuf//proto:go_default_library", "@org_golang_google_protobuf//types/known/anypb:go_default_library", "@org_golang_google_protobuf//types/known/durationpb:go_default_library", + "@org_golang_google_protobuf//types/known/structpb:go_default_library", "@org_golang_google_protobuf//types/known/timestamppb:go_default_library", ], ) diff --git a/vendor/github.com/google/cel-go/common/types/bool.go b/vendor/github.com/google/cel-go/common/types/bool.go index 1f9e10739..5f1e4573e 100644 --- a/vendor/github.com/google/cel-go/common/types/bool.go +++ b/vendor/github.com/google/cel-go/common/types/bool.go @@ -69,7 +69,7 @@ func (b Bool) ConvertToNative(typeDesc reflect.Type) (any, error) { case boolWrapperType: // Convert the bool to a wrapperspb.BoolValue. return wrapperspb.Bool(bool(b)), nil - case jsonValueType: + case JSONValueType: // Return the bool as a new structpb.Value. return structpb.NewBoolValue(bool(b)), nil default: diff --git a/vendor/github.com/google/cel-go/common/types/bytes.go b/vendor/github.com/google/cel-go/common/types/bytes.go index b59e1fc20..2eefb5d7f 100644 --- a/vendor/github.com/google/cel-go/common/types/bytes.go +++ b/vendor/github.com/google/cel-go/common/types/bytes.go @@ -44,7 +44,10 @@ func (b Bytes) Add(other ref.Val) ref.Val { if !ok { return ValOrErr(other, "no such overload") } - return append(b, otherBytes...) + sum := make([]byte, 0, len(b)+len(otherBytes)) + sum = append(sum, b...) + sum = append(sum, otherBytes...) + return Bytes(sum) } // Compare implements traits.Comparer interface method by lexicographic ordering. @@ -79,7 +82,7 @@ func (b Bytes) ConvertToNative(typeDesc reflect.Type) (any, error) { case byteWrapperType: // Convert the bytes to a wrapperspb.BytesValue. return wrapperspb.Bytes([]byte(b)), nil - case jsonValueType: + case JSONValueType: // CEL follows the proto3 to JSON conversion by encoding bytes to a string via base64. // The encoding below matches the golang 'encoding/json' behavior during marshaling, // which uses base64.StdEncoding. diff --git a/vendor/github.com/google/cel-go/common/types/double.go b/vendor/github.com/google/cel-go/common/types/double.go index 1e7de9d6e..02abfee2d 100644 --- a/vendor/github.com/google/cel-go/common/types/double.go +++ b/vendor/github.com/google/cel-go/common/types/double.go @@ -89,7 +89,7 @@ func (d Double) ConvertToNative(typeDesc reflect.Type) (any, error) { case floatWrapperType: // Convert to a wrapperspb.FloatValue (with truncation). return wrapperspb.Float(float32(d)), nil - case jsonValueType: + case JSONValueType: // Note, there are special cases for proto3 to json conversion that // expect the floating point value to be converted to a NaN, // Infinity, or -Infinity string values, but the jsonpb string diff --git a/vendor/github.com/google/cel-go/common/types/duration.go b/vendor/github.com/google/cel-go/common/types/duration.go index be58d567e..220714773 100644 --- a/vendor/github.com/google/cel-go/common/types/duration.go +++ b/vendor/github.com/google/cel-go/common/types/duration.go @@ -106,7 +106,7 @@ func (d Duration) ConvertToNative(typeDesc reflect.Type) (any, error) { case durationValueType: // Unwrap the CEL value to its underlying proto value. return dpb.New(d.Duration), nil - case jsonValueType: + case JSONValueType: // CEL follows the proto3 to JSON conversion. // Note, using jsonpb would wrap the result in extra double quotes. v := d.ConvertToType(StringType) diff --git a/vendor/github.com/google/cel-go/common/types/err.go b/vendor/github.com/google/cel-go/common/types/err.go index 17ab1a95e..3216ff1c4 100644 --- a/vendor/github.com/google/cel-go/common/types/err.go +++ b/vendor/github.com/google/cel-go/common/types/err.go @@ -113,6 +113,9 @@ func ValOrErr(val ref.Val, format string, args ...any) ref.Val { // WrapErr wraps an existing Go error value into a CEL Err value. func WrapErr(err error) ref.Val { + if err, ok := err.(*Err); ok { + return err + } return &Err{error: err} } diff --git a/vendor/github.com/google/cel-go/common/types/int.go b/vendor/github.com/google/cel-go/common/types/int.go index 0ac1997b7..60d5a7160 100644 --- a/vendor/github.com/google/cel-go/common/types/int.go +++ b/vendor/github.com/google/cel-go/common/types/int.go @@ -120,7 +120,7 @@ func (i Int) ConvertToNative(typeDesc reflect.Type) (any, error) { case int64WrapperType: // Convert the value to a wrapperspb.Int64Value. return wrapperspb.Int64(int64(i)), nil - case jsonValueType: + case JSONValueType: // The proto-to-JSON conversion rules would convert all 64-bit integer values to JSON // decimal strings. Because CEL ints might come from the automatic widening of 32-bit // values in protos, the JSON type is chosen dynamically based on the value. diff --git a/vendor/github.com/google/cel-go/common/types/json_value.go b/vendor/github.com/google/cel-go/common/types/json_value.go index 13a4efe7a..90acfe7df 100644 --- a/vendor/github.com/google/cel-go/common/types/json_value.go +++ b/vendor/github.com/google/cel-go/common/types/json_value.go @@ -22,8 +22,9 @@ import ( // JSON type constants representing the reflected types of protobuf JSON values. var ( - jsonValueType = reflect.TypeOf(&structpb.Value{}) - jsonListValueType = reflect.TypeOf(&structpb.ListValue{}) - jsonStructType = reflect.TypeOf(&structpb.Struct{}) - jsonNullType = reflect.TypeOf(structpb.NullValue_NULL_VALUE) + // JSONValueType describes the protobuf native type for a JSON value. + JSONValueType = reflect.TypeFor[*structpb.Value]() + JSONListType = reflect.TypeFor[*structpb.ListValue]() + JSONStructType = reflect.TypeFor[*structpb.Struct]() + JSONNullType = reflect.TypeFor[structpb.NullValue]() ) diff --git a/vendor/github.com/google/cel-go/common/types/list.go b/vendor/github.com/google/cel-go/common/types/list.go index 8c023f891..028770ed6 100644 --- a/vendor/github.com/google/cel-go/common/types/list.go +++ b/vendor/github.com/google/cel-go/common/types/list.go @@ -126,16 +126,7 @@ func (l *baseList) Add(other ref.Val) ref.Val { if !ok { return MaybeNoSuchOverloadErr(other) } - if l.Size() == IntZero { - return other - } - if otherList.Size() == IntZero { - return l - } - return &concatList{ - Adapter: l.Adapter, - prevList: l, - nextList: otherList} + return newConcatList(l.Adapter, l, otherList) } // Contains implements the traits.Container interface method. @@ -153,6 +144,9 @@ func (l *baseList) Contains(elem ref.Val) ref.Val { // ConvertToNative implements the ref.Val interface method. func (l *baseList) ConvertToNative(typeDesc reflect.Type) (any, error) { + if typeDesc == reflect.TypeFor[any]() { + typeDesc = reflect.TypeFor[[]any]() + } // If the underlying list value is assignable to the reflected type return it. if reflect.TypeOf(l.value).AssignableTo(typeDesc) { return l.value, nil @@ -164,19 +158,19 @@ func (l *baseList) ConvertToNative(typeDesc reflect.Type) (any, error) { // Attempt to convert the list to a set of well known protobuf types. switch typeDesc { case anyValueType: - json, err := l.ConvertToNative(jsonListValueType) + json, err := l.ConvertToNative(JSONListType) if err != nil { return nil, err } return anypb.New(json.(proto.Message)) - case jsonValueType, jsonListValueType: + case JSONValueType, JSONListType: jsonValues, err := l.ConvertToNative(reflect.TypeOf([]*structpb.Value{})) if err != nil { return nil, err } jsonList := &structpb.ListValue{Values: jsonValues.([]*structpb.Value)} - if typeDesc == jsonListValueType { + if typeDesc == JSONListType { return jsonList, nil } return structpb.NewListValue(jsonList), nil @@ -350,9 +344,27 @@ func (l *mutableList) ToImmutableList() traits.Lister { // The `Adapter` enables native type to CEL type conversions. type concatList struct { Adapter - value any - prevList traits.Lister - nextList traits.Lister + value any + prevList traits.Lister + nextList traits.Lister + cachedSize ref.Val +} + +func newConcatList(adapter Adapter, prevList, nextList traits.Lister) ref.Val { + prevSize := prevList.Size().(Int) + nextSize := nextList.Size().(Int) + if prevSize == IntZero { + return nextList.(ref.Val) + } + if nextSize == IntZero { + return prevList.(ref.Val) + } + return &concatList{ + Adapter: adapter, + prevList: prevList, + nextList: nextList, + cachedSize: prevSize.Add(nextSize), + } } // Add implements the traits.Adder interface method. @@ -361,16 +373,7 @@ func (l *concatList) Add(other ref.Val) ref.Val { if !ok { return MaybeNoSuchOverloadErr(other) } - if l.Size() == IntZero { - return other - } - if otherList.Size() == IntZero { - return l - } - return &concatList{ - Adapter: l.Adapter, - prevList: l, - nextList: otherList} + return newConcatList(l.Adapter, l, otherList) } // Contains implements the traits.Container interface method. @@ -474,7 +477,7 @@ func (l *concatList) Iterator() traits.Iterator { // Size implements the traits.Sizer interface method. func (l *concatList) Size() ref.Val { - return l.prevList.Size().(Int).Add(l.nextList.Size()) + return l.cachedSize } // String converts the concatenated list to a human-readable string. diff --git a/vendor/github.com/google/cel-go/common/types/map.go b/vendor/github.com/google/cel-go/common/types/map.go index b33096197..e4d6f7657 100644 --- a/vendor/github.com/google/cel-go/common/types/map.go +++ b/vendor/github.com/google/cel-go/common/types/map.go @@ -19,8 +19,8 @@ import ( "reflect" "sort" "strings" + "unicode" - "github.com/stoewer/go-strcase" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" @@ -156,6 +156,9 @@ func (m *baseMap) Contains(index ref.Val) ref.Val { func (m *baseMap) ConvertToNative(typeDesc reflect.Type) (any, error) { // If the map is already assignable to the desired type return it, e.g. interfaces and // maps with the same key value types. + if typeDesc == reflect.TypeFor[any]() { + typeDesc = reflect.TypeFor[map[any]any]() + } if reflect.TypeOf(m.value).AssignableTo(typeDesc) { return m.value, nil } @@ -164,19 +167,19 @@ func (m *baseMap) ConvertToNative(typeDesc reflect.Type) (any, error) { } switch typeDesc { case anyValueType: - json, err := m.ConvertToNative(jsonStructType) + json, err := m.ConvertToNative(JSONStructType) if err != nil { return nil, err } return anypb.New(json.(proto.Message)) - case jsonValueType, jsonStructType: + case JSONValueType, JSONStructType: jsonEntries, err := m.ConvertToNative(reflect.TypeOf(map[string]*structpb.Value{})) if err != nil { return nil, err } jsonMap := &structpb.Struct{Fields: jsonEntries.(map[string]*structpb.Value)} - if typeDesc == jsonStructType { + if typeDesc == JSONStructType { return jsonMap, nil } return structpb.NewStructValue(jsonMap), nil @@ -226,7 +229,7 @@ func (m *baseMap) ConvertToNative(typeDesc reflect.Type) (any, error) { return nil, fieldName.(*Err) } name := string(fieldName.(String)) - name = strcase.UpperCamelCase(name) + name = upperCamelCase(name) fieldRef := nativeStruct.FieldByName(name) if !fieldRef.IsValid() { return nil, fmt.Errorf("type conversion error, no such field '%s' in type '%v'", name, typeDesc) @@ -703,12 +706,12 @@ func (m *protoMap) ConvertToNative(typeDesc reflect.Type) (any, error) { // maps with the same key value types. switch typeDesc { case anyValueType: - json, err := m.ConvertToNative(jsonStructType) + json, err := m.ConvertToNative(JSONStructType) if err != nil { return nil, err } return anypb.New(json.(proto.Message)) - case jsonValueType, jsonStructType: + case JSONValueType, JSONStructType: jsonEntries, err := m.ConvertToNative(reflect.TypeOf(map[string]*structpb.Value{})) if err != nil { @@ -716,7 +719,7 @@ func (m *protoMap) ConvertToNative(typeDesc reflect.Type) (any, error) { } jsonMap := &structpb.Struct{ Fields: jsonEntries.(map[string]*structpb.Value)} - if typeDesc == jsonStructType { + if typeDesc == JSONStructType { return jsonMap, nil } return structpb.NewStructValue(jsonMap), nil @@ -1036,3 +1039,32 @@ func InsertMapKeyValue(m traits.Mapper, k, v ref.Val) ref.Val { } return NewErr("insert failed: key %v already exists", k) } + +func upperCamelCase(s string) string { + var newStr strings.Builder + s = strings.TrimSpace(s) + var prev rune + for _, curr := range s { + if prev == 0 || isDelim(prev) { + if !isDelim(curr) { + newStr.WriteRune(unicode.ToUpper(curr)) + } + } else if !isDelim(curr) { + if isLower(prev) { + newStr.WriteRune(curr) + } else { + newStr.WriteRune(unicode.ToLower(curr)) + } + } + prev = curr + } + return newStr.String() +} + +func isDelim(r rune) bool { + return r == '_' || r == '-' +} + +func isLower(r rune) bool { + return r >= 'a' && r <= 'z' +} diff --git a/vendor/github.com/google/cel-go/common/types/native.go b/vendor/github.com/google/cel-go/common/types/native.go new file mode 100644 index 000000000..33c897b43 --- /dev/null +++ b/vendor/github.com/google/cel-go/common/types/native.go @@ -0,0 +1,583 @@ +// Copyright 2022 Google LLC +// +// 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 me. +// limitations under the License. + +package types + +import ( + "errors" + "fmt" + "reflect" + "strings" + "time" + + "google.golang.org/protobuf/reflect/protoreflect" + + "github.com/google/cel-go/common/types/ref" + "github.com/google/cel-go/common/types/traits" + + structpb "google.golang.org/protobuf/types/known/structpb" +) + +var ( + nativeObjTraitMask = traits.FieldTesterType | traits.IndexerType + jsonValueType = reflect.TypeFor[*structpb.Value]() + jsonStructType = reflect.TypeFor[*structpb.Struct]() + + pbMsgInterfaceType = reflect.TypeFor[protoreflect.ProtoMessage]() + refValType = reflect.TypeFor[ref.Val]() + timestampType = reflect.TypeFor[time.Time]() + durationType = reflect.TypeFor[time.Duration]() + + errDuplicatedFieldName = errors.New("field name already exists in struct") +) + +// NewNativeType constructs a NativeType instance for a Go struct reflect.Type. +func NewNativeType(rawType reflect.Type, opts ...NativeTypeOption) (*NativeType, error) { + tpOptions := NativeTypeOptions{} + for _, opt := range opts { + if err := opt(&tpOptions); err != nil { + return nil, err + } + } + return newNativeType(rawType, tpOptions.fieldNameHandler) +} + +// 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. +type NativeTypesFieldNameHandler = func(field reflect.StructField) string + +// NativeTypeOptions holds options for native types. +type NativeTypeOptions struct { + fieldNameHandler NativeTypesFieldNameHandler +} + +// NativeTypeOption is a functional option for configuring handling of native types. +type NativeTypeOption func(*NativeTypeOptions) error + +// ParseStructTags configures if native types field names should be overridable by CEL struct tags. +// This is equivalent to ParseStructTag("cel"). +func ParseStructTags(enabled bool) NativeTypeOption { + if enabled { + return ParseStructTag("cel") + } + return ParseStructField(nil) +} + +// ParseStructTag configures the struct tag to parse. The 0th item in the tag is used as the name of the CEL field. +func ParseStructTag(tag string) NativeTypeOption { + return ParseStructField(fieldNameByTag(tag)) +} + +// ParseStructField configures how to parse Go struct fields. It can be used to customize struct field parsing. +func ParseStructField(handler NativeTypesFieldNameHandler) NativeTypeOption { + return func(opts *NativeTypeOptions) error { + opts.fieldNameHandler = handler + return nil + } +} + +func fieldNameByTag(structTagToParse string) func(field reflect.StructField) string { + return func(field reflect.StructField) string { + tag, found := field.Tag.Lookup(structTagToParse) + if found { + splits := strings.Split(tag, ",") + if len(splits) > 0 { + name := splits[0] + return name + } + } + return field.Name + } +} + +func isSkippedFieldName(name string) bool { + return name == "" || name == "-" +} + +// NativeType represents a CEL struct type descriptor generated from a native Go struct. +type NativeType struct { + typeName string + refType reflect.Type + fieldsByName map[string]reflect.StructField +} + +// ReflectType implements StructTypeDescriptor. +func (t *NativeType) ReflectType() reflect.Type { + return t.refType +} + +// Adapt implements StructTypeDescriptor. +func (t *NativeType) Adapt(adapter Adapter, value any) ref.Val { + if value == nil { + return NullValue + } + refVal := reflect.ValueOf(value) + if refVal.Kind() == reflect.Ptr { + if refVal.IsNil() { + return NullValue + } + refVal = refVal.Elem() + } + return &nativeObj{ + Adapter: adapter, + val: value, + valType: t, + refValue: refVal, + } +} + +// ConvertToNative implements ref.Val.ConvertToNative. +func (t *NativeType) ConvertToNative(typeDesc reflect.Type) (any, error) { + return nil, fmt.Errorf("type conversion error for type to '%v'", typeDesc) +} + +// ConvertToType implements ref.Val.ConvertToType. +func (t *NativeType) ConvertToType(typeVal ref.Type) ref.Val { + switch typeVal { + case TypeType: + return TypeType + } + return NewErr("type conversion error from '%s' to '%s'", TypeType, typeVal) +} + +// Equal returns true if both type names are equal to each other. +func (t *NativeType) Equal(other ref.Val) ref.Val { + otherType, ok := other.(ref.Type) + return Bool(ok && t.TypeName() == otherType.TypeName()) +} + +// HasTrait implements the ref.Type interface method. +func (t *NativeType) HasTrait(trait int) bool { + return nativeObjTraitMask&trait == trait +} + +// String implements the fmt.Stringer interface method. +func (t *NativeType) String() string { + return t.typeName +} + +// Type implements the ref.Val interface method. +func (t *NativeType) Type() ref.Type { + return TypeType +} + +// TypeName implements the ref.Type interface method. +func (t *NativeType) TypeName() string { + return t.typeName +} + +// Value implements the ref.Val interface method. +func (t *NativeType) Value() any { + return t.typeName +} + +func (t *NativeType) hasField(fieldName string) (reflect.StructField, bool) { + f, found := t.fieldsByName[fieldName] + if !found { + return reflect.StructField{}, false + } + return f, true +} + +// FieldNames provides the list of field names for this type. +func (t *NativeType) FieldNames() []string { + fields := make([]string, 0, len(t.fieldsByName)) + for fieldName := range t.fieldsByName { + fields = append(fields, fieldName) + } + return fields +} + +// FindFieldType looks up a field by name and provides the type and accessors. +func (t *NativeType) FindFieldType(fieldName string) (*FieldType, bool) { + refField, found := t.hasField(fieldName) + if !found { + return nil, false + } + celType, ok := convertToCelType(refField.Type) + if !ok { + return nil, false + } + return &FieldType{ + Type: celType, + IsSet: func(obj any) bool { + refVal := reflect.Indirect(reflect.ValueOf(obj)) + refFieldVal := safeGetFieldByIndex(refVal, refField.Index) + return refFieldVal.IsValid() && !refFieldVal.IsZero() + }, + GetFrom: func(obj any) (any, error) { + refVal := reflect.Indirect(reflect.ValueOf(obj)) + refFieldVal := safeGetFieldByIndex(refVal, refField.Index) + return getFieldValue(refFieldVal), nil + }, + }, true +} + +// NewValue constructs a new native Go struct instance populated with given field values. +func (t *NativeType) NewValue(adapter Adapter, fields map[string]ref.Val) ref.Val { + refPtr := reflect.New(t.refType) + refVal := refPtr.Elem() + for fieldName, val := range fields { + refFieldDef, isDefined := t.hasField(fieldName) + if !isDefined { + return NewErr("no such field: %s", fieldName) + } + fieldVal, err := val.ConvertToNative(refFieldDef.Type) + if err != nil { + return NewErrFromString(err.Error()) + } + refField := safeSetFieldByIndex(refVal, refFieldDef.Index) + if !refField.IsValid() { + return NewErr("cannot set field: %s", fieldName) + } + refField.Set(reflect.ValueOf(fieldVal)) + } + return adapter.NativeToValue(refPtr.Interface()) +} + +type nativeObj struct { + Adapter + val any + valType *NativeType + refValue reflect.Value +} + +func (o *nativeObj) ConvertToNative(typeDesc reflect.Type) (any, error) { + if o.refValue.Type() == typeDesc { + if reflect.TypeOf(o.val) == typeDesc { + return o.val, nil + } + return o.refValue.Interface(), nil + } + if typeDesc.Kind() == reflect.Pointer && o.refValue.Type() == typeDesc.Elem() { + if reflect.TypeOf(o.val) == typeDesc { + return o.val, nil + } + ptr := reflect.New(o.refValue.Type()) + ptr.Elem().Set(o.refValue) + return ptr.Interface(), nil + } + switch typeDesc { + case jsonValueType: + jsonStruct, err := o.ConvertToNative(jsonStructType) + if err != nil { + return nil, err + } + return structpb.NewStructValue(jsonStruct.(*structpb.Struct)), nil + case jsonStructType: + refVal := reflect.Indirect(o.refValue) + fields := make(map[string]*structpb.Value, refVal.NumField()) + for fieldName, fieldType := range o.valType.fieldsByName { + fieldValue := safeGetFieldByIndex(refVal, fieldType.Index) + if !fieldValue.IsValid() || fieldValue.IsZero() { + continue + } + fieldCELVal := o.NativeToValue(fieldValue.Interface()) + fieldJSONVal, err := fieldCELVal.ConvertToNative(jsonValueType) + if err != nil { + return nil, err + } + fields[fieldName] = fieldJSONVal.(*structpb.Value) + } + return &structpb.Struct{Fields: fields}, nil + } + return nil, fmt.Errorf("type conversion error from '%v' to '%v'", o.Type(), typeDesc) +} + +func (o *nativeObj) ConvertToType(typeVal ref.Type) ref.Val { + switch typeVal { + case TypeType: + return o.valType + default: + if typeVal.TypeName() == o.valType.typeName { + return o + } + } + return NewErr("type conversion error from '%s' to '%s'", o.Type(), typeVal) +} + +func (o *nativeObj) Equal(other ref.Val) ref.Val { + otherNtv, ok := other.(*nativeObj) + if !ok { + return False + } + val := o.val + otherVal := otherNtv.val + if reflect.TypeOf(val).Kind() != reflect.TypeOf(otherVal).Kind() { + val = o.refValue.Interface() + otherVal = otherNtv.refValue.Interface() + } + return Bool(reflect.DeepEqual(val, otherVal)) +} + +func (o *nativeObj) IsZeroValue() bool { + return o.refValue.IsZero() +} + +func (o *nativeObj) IsSet(field ref.Val) ref.Val { + refField, refErr := o.getReflectedField(field) + if refErr != nil { + return refErr + } + return Bool(!refField.IsZero()) +} + +func (o *nativeObj) Get(field ref.Val) ref.Val { + refField, refErr := o.getReflectedField(field) + if refErr != nil { + return refErr + } + return adaptFieldValue(o, refField) +} + +func (o *nativeObj) getReflectedField(field ref.Val) (reflect.Value, ref.Val) { + fieldName, ok := field.(String) + if !ok { + return reflect.Value{}, MaybeNoSuchOverloadErr(field) + } + fieldNameStr := string(fieldName) + refField, isDefined := o.valType.hasField(fieldNameStr) + if !isDefined { + return reflect.Value{}, NewErr("no such field: %s", fieldName) + } + refVal := reflect.Indirect(o.refValue) + return safeGetFieldByIndex(refVal, refField.Index), nil +} + +func (o *nativeObj) Type() ref.Type { + return o.valType +} + +func (o *nativeObj) Value() any { + return o.val +} + +func newNativeTypes(rawType reflect.Type, fieldNameHandler NativeTypesFieldNameHandler) ([]*NativeType, error) { + nt, err := newNativeType(rawType, fieldNameHandler) + if err != nil { + return nil, err + } + result := []*NativeType{nt} + + alreadySeen := make(map[string]struct{}) + var iterateStructMembers func(reflect.Type) + iterateStructMembers = func(t reflect.Type) { + if t.Implements(reflect.TypeFor[ref.Val]()) { + return + } + if k := t.Kind(); k == reflect.Pointer || k == reflect.Slice || k == reflect.Array || k == reflect.Map { + iterateStructMembers(t.Elem()) + return + } + if t.Kind() != reflect.Struct { + return + } + if _, seen := alreadySeen[t.String()]; seen { + return + } + alreadySeen[t.String()] = struct{}{} + nt, ntErr := newNativeType(t, fieldNameHandler) + if ntErr != nil { + err = ntErr + return + } + result = append(result, nt) + + for _, field := range reflect.VisibleFields(t) { + if !field.IsExported() || !isSupportedType(field.Type) { + continue + } + iterateStructMembers(field.Type) + } + } + iterateStructMembers(rawType) + + return result, err +} + +func toFieldName(f reflect.StructField, fieldNameHandler NativeTypesFieldNameHandler) string { + if fieldNameHandler == nil { + return f.Name + } + return fieldNameHandler(f) +} + +func newNativeType(rawType reflect.Type, fieldNameHandler NativeTypesFieldNameHandler) (*NativeType, error) { + refType := rawType + if refType.Kind() == reflect.Pointer { + refType = refType.Elem() + } + if !isValidObjectType(refType) { + return nil, fmt.Errorf("unsupported reflect.Type %v, must be reflect.Struct", rawType) + } + + fieldsByName := make(map[string]reflect.StructField) + for _, field := range reflect.VisibleFields(refType) { + if !field.IsExported() || !isSupportedType(field.Type) { + continue + } + fieldName := toFieldName(field, fieldNameHandler) + if isSkippedFieldName(fieldName) { + continue + } + if _, found := fieldsByName[fieldName]; found { + return nil, fmt.Errorf("invalid field name `%s` in struct `%s`: %w", fieldName, refType.Name(), errDuplicatedFieldName) + } + fieldsByName[fieldName] = field + } + + return &NativeType{ + typeName: fmt.Sprintf("%s.%s", simplePkgAlias(refType.PkgPath()), refType.Name()), + refType: refType, + fieldsByName: fieldsByName, + }, nil +} + +func adaptFieldValue(adapter Adapter, refField reflect.Value) ref.Val { + return adapter.NativeToValue(getFieldValue(refField)) +} + +func safeSetFieldByIndex(v reflect.Value, index []int) reflect.Value { + for _, i := range index { + if v.Kind() == reflect.Pointer { + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + v = v.Elem() + } + if v.Kind() != reflect.Struct || i >= v.NumField() { + return reflect.Value{} + } + v = v.Field(i) + } + return v +} + +func safeGetFieldByIndex(v reflect.Value, index []int) reflect.Value { + for _, i := range index { + if v.Kind() == reflect.Pointer { + if v.IsNil() { + v = reflect.New(v.Type().Elem()).Elem() + } else { + v = v.Elem() + } + } + if v.Kind() != reflect.Struct || i >= v.NumField() { + return reflect.Value{} + } + v = v.Field(i) + } + return v +} + +func getFieldValue(refField reflect.Value) any { + if !refField.IsValid() { + return nil + } + if refField.IsZero() { + switch refField.Kind() { + case reflect.Struct: + if refField.Type() == timestampType { + return time.Unix(0, 0) + } + case reflect.Pointer: + return reflect.New(refField.Type().Elem()).Interface() + } + } + return refField.Interface() +} + +func simplePkgAlias(pkgPath string) string { + paths := strings.Split(pkgPath, "/") + if len(paths) == 0 { + return "" + } + return paths[len(paths)-1] +} + +func isValidObjectType(refType reflect.Type) bool { + return refType.Kind() == reflect.Struct +} + +func isSupportedType(refType reflect.Type) bool { + switch refType.Kind() { + case reflect.Chan, reflect.Complex64, reflect.Complex128, reflect.Func, reflect.UnsafePointer, reflect.Uintptr: + return false + case reflect.Array, reflect.Slice: + return isSupportedType(refType.Elem()) + case reflect.Map: + return isSupportedType(refType.Key()) && isSupportedType(refType.Elem()) + } + return true +} + +func convertToCelType(refType reflect.Type) (*Type, bool) { + switch refType.Kind() { + case reflect.Bool: + return BoolType, true + case reflect.Float32, reflect.Float64: + return DoubleType, true + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if refType == durationType { + return DurationType, true + } + return IntType, true + case reflect.String: + return StringType, true + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return UintType, true + case reflect.Array, reflect.Slice: + refElem := refType.Elem() + if refElem == reflect.TypeOf(byte(0)) { + return BytesType, true + } + elemType, ok := convertToCelType(refElem) + if !ok { + return nil, false + } + return NewListType(elemType), true + case reflect.Map: + keyType, ok := convertToCelType(refType.Key()) + if !ok { + return nil, false + } + elemType, ok := convertToCelType(refType.Elem()) + if !ok { + return nil, false + } + return NewMapType(keyType, elemType), true + case reflect.Struct: + if refType == timestampType { + return TimestampType, true + } + if refType.Implements(refValType) { + emptyCelVal := reflect.New(refType).Elem().Interface().(ref.Val) + return emptyCelVal.Type().(*Type), true + } + return NewObjectType( + fmt.Sprintf("%s.%s", simplePkgAlias(refType.PkgPath()), refType.Name()), + ), true + case reflect.Pointer: + if refType.Implements(refValType) { + emptyCelVal := reflect.New(refType.Elem()).Interface().(ref.Val) + return emptyCelVal.Type().(*Type), true + } + if refType.Implements(pbMsgInterfaceType) { + pbMsg := reflect.New(refType.Elem()).Interface().(protoreflect.ProtoMessage) + return NewObjectType(string(pbMsg.ProtoReflect().Descriptor().FullName())), true + } + return convertToCelType(refType.Elem()) + } + return nil, false +} diff --git a/vendor/github.com/google/cel-go/common/types/null.go b/vendor/github.com/google/cel-go/common/types/null.go index 2c0297fe6..671e1ee5c 100644 --- a/vendor/github.com/google/cel-go/common/types/null.go +++ b/vendor/github.com/google/cel-go/common/types/null.go @@ -45,7 +45,7 @@ func (n Null) ConvertToNative(typeDesc reflect.Type) (any, error) { switch typeDesc.Kind() { case reflect.Int32: switch typeDesc { - case jsonNullType: + case JSONNullType: return structpb.NullValue_NULL_VALUE, nil case nullReflectType: return n, nil @@ -55,18 +55,18 @@ func (n Null) ConvertToNative(typeDesc reflect.Type) (any, error) { case anyValueType: // Convert to a JSON-null before packing to an Any field since the enum value for JSON // null cannot be packed directly. - pb, err := n.ConvertToNative(jsonValueType) + pb, err := n.ConvertToNative(JSONValueType) if err != nil { return nil, err } return anypb.New(pb.(proto.Message)) - case jsonValueType: + case JSONValueType: return structpb.NewNullValue(), nil case boolWrapperType, byteWrapperType, doubleWrapperType, floatWrapperType, int32WrapperType, int64WrapperType, stringWrapperType, uint32WrapperType, uint64WrapperType, durationValueType, timestampValueType, protoIfaceType: return nil, nil - case jsonListValueType, jsonStructType: + case JSONListType, JSONStructType: // skip handling default: if typeDesc.Implements(protoIfaceType) { diff --git a/vendor/github.com/google/cel-go/common/types/object.go b/vendor/github.com/google/cel-go/common/types/object.go index 776f6954a..bb2a09e87 100644 --- a/vendor/github.com/google/cel-go/common/types/object.go +++ b/vendor/github.com/google/cel-go/common/types/object.go @@ -71,7 +71,7 @@ func (o *protoObj) ConvertToNative(typeDesc reflect.Type) (any, error) { return srcPB, nil } return anypb.New(srcPB) - case jsonValueType: + case JSONValueType: // Marshal the proto to JSON first, and then rehydrate as protobuf.Value as there is no // support for direct conversion from proto.Message to protobuf.Value. bytes, err := protojson.Marshal(srcPB) @@ -187,8 +187,14 @@ func (o *protoObj) format(sb *strings.Builder) { if i > 0 { sb.WriteString(", ") } - sb.WriteString(fmt.Sprintf("%s: ", field.Name())) - formatTo(sb, o.Get(String(field.Name()))) + name := String(field.Name()) + if field.IsExtension() { + name = String(field.FullName()) + fmt.Fprintf(sb, "`%s`: ", name) + } else { + fmt.Fprintf(sb, "%s: ", name) + } + formatTo(sb, o.Get(name)) } sb.WriteString("}") } diff --git a/vendor/github.com/google/cel-go/common/types/optional.go b/vendor/github.com/google/cel-go/common/types/optional.go index b8685ebf5..0d861823d 100644 --- a/vendor/github.com/google/cel-go/common/types/optional.go +++ b/vendor/github.com/google/cel-go/common/types/optional.go @@ -25,7 +25,7 @@ import ( var ( // OptionalType indicates the runtime type of an optional value. - OptionalType = NewOpaqueType("optional_type") + OptionalType = NewOpaqueType("optional_type", DynType) // OptionalNone is a sentinel value which is used to indicate an empty optional value. OptionalNone = &Optional{} @@ -59,6 +59,9 @@ func (o *Optional) ConvertToNative(typeDesc reflect.Type) (any, error) { if !o.HasValue() { return nil, errors.New("optional.none() dereference") } + if typeDesc == reflect.TypeFor[*Optional]() { + return o, nil + } return o.value.ConvertToNative(typeDesc) } diff --git a/vendor/github.com/google/cel-go/common/types/pb/file.go b/vendor/github.com/google/cel-go/common/types/pb/file.go index e323afb1d..3a8bdf0b2 100644 --- a/vendor/github.com/google/cel-go/common/types/pb/file.go +++ b/vendor/github.com/google/cel-go/common/types/pb/file.go @@ -32,7 +32,7 @@ func newFileDescription(fileDesc protoreflect.FileDescriptor, pbdb *Db) (*FileDe } types := make(map[string]*TypeDescription) for name, msgType := range metadata.msgTypes { - types[name] = newTypeDescription(name, msgType, pbdb.extensions) + types[name] = newTypeDescription(name, msgType, pbdb) } fileExtMap := make(extensionMap) for typeName, extensions := range metadata.msgExtensionMap { @@ -42,12 +42,13 @@ func newFileDescription(fileDesc protoreflect.FileDescriptor, pbdb *Db) (*FileDe } for _, ext := range extensions { extDesc := dynamicpb.NewExtensionType(ext).TypeDescriptor() - messageExtMap[string(ext.FullName())] = newFieldDescription(extDesc) + messageExtMap[string(ext.FullName())] = newFieldDescription(extDesc, pbdb.jsonFieldNames) } fileExtMap[typeName] = messageExtMap } return &FileDescription{ name: fileDesc.Path(), + desc: fileDesc, types: types, enums: enums, }, fileExtMap @@ -56,6 +57,7 @@ func newFileDescription(fileDesc protoreflect.FileDescriptor, pbdb *Db) (*FileDe // FileDescription holds a map of all types and enum values declared within a proto file. type FileDescription struct { name string + desc protoreflect.FileDescriptor types map[string]*TypeDescription enums map[string]*EnumValueDescription } @@ -68,6 +70,7 @@ func (fd *FileDescription) Copy(pbdb *Db) *FileDescription { } return &FileDescription{ name: fd.name, + desc: fd.desc, types: typesCopy, enums: fd.enums, } @@ -78,6 +81,11 @@ func (fd *FileDescription) GetName() string { return fd.name } +// FileDescriptor returns the proto file descriptor associated with the file representation. +func (fd *FileDescription) FileDescriptor() protoreflect.FileDescriptor { + return fd.desc +} + // GetEnumDescription returns an EnumDescription for a qualified enum value // name declared within the .proto file. func (fd *FileDescription) GetEnumDescription(enumName string) (*EnumValueDescription, bool) { diff --git a/vendor/github.com/google/cel-go/common/types/pb/pb.go b/vendor/github.com/google/cel-go/common/types/pb/pb.go index eadebcb04..c6fdfc695 100644 --- a/vendor/github.com/google/cel-go/common/types/pb/pb.go +++ b/vendor/github.com/google/cel-go/common/types/pb/pb.go @@ -42,6 +42,9 @@ type Db struct { files []*FileDescription // extensions contains the mapping between a given type name, extension name and its FieldDescription extensions map[string]map[string]*FieldDescription + + // jsonFieldNames indicates whether json-style names are supported as proto field names. + jsonFieldNames bool } // extensionsMap is a type alias to a map[typeName]map[extensionName]*FieldDescription @@ -81,13 +84,27 @@ func Merge(dstPB, srcPB proto.Message) error { return nil } +// DbOption modifies feature flags enabled on the proto database. +type DbOption func(*Db) *Db + +// JSONFieldNames configures the Db to support proto field accesses by their JSON names. +func JSONFieldNames(enabled bool) DbOption { + return func(db *Db) *Db { + db.jsonFieldNames = enabled + return db + } +} + // NewDb creates a new `pb.Db` with an empty type name to file description map. -func NewDb() *Db { +func NewDb(opts ...DbOption) *Db { pbdb := &Db{ revFileDescriptorMap: make(map[string]*FileDescription), files: []*FileDescription{}, extensions: make(extensionMap), } + for _, o := range opts { + pbdb = o(pbdb) + } // The FileDescription objects in the default db contain lazily initialized TypeDescription // values which may point to the state contained in the DefaultDb irrespective of this shallow // copy; however, the type graph for a field is idempotently computed, and is guaranteed to @@ -100,9 +117,15 @@ func NewDb() *Db { return pbdb } +// JSONFieldNames indicates whether the database is configured for proto field accesses by JSON names. +func (pbdb *Db) JSONFieldNames() bool { + return pbdb.jsonFieldNames +} + // Copy creates a copy of the current database with its own internal descriptor mapping. func (pbdb *Db) Copy() *Db { copy := NewDb() + copy.jsonFieldNames = pbdb.jsonFieldNames for _, fd := range pbdb.files { hasFile := false for _, fd2 := range copy.files { diff --git a/vendor/github.com/google/cel-go/common/types/pb/type.go b/vendor/github.com/google/cel-go/common/types/pb/type.go index bdd474c95..8d7d1b299 100644 --- a/vendor/github.com/google/cel-go/common/types/pb/type.go +++ b/vendor/github.com/google/cel-go/common/types/pb/type.go @@ -40,68 +40,92 @@ type description interface { // newTypeDescription produces a TypeDescription value for the fully-qualified proto type name // with a given descriptor. -func newTypeDescription(typeName string, desc protoreflect.MessageDescriptor, extensions extensionMap) *TypeDescription { +func newTypeDescription(typeName string, desc protoreflect.MessageDescriptor, pbdb *Db) *TypeDescription { msgType := dynamicpb.NewMessageType(desc) msgZero := dynamicpb.NewMessage(desc) fieldMap := map[string]*FieldDescription{} + jsonFieldMap := map[string]*FieldDescription{} fields := desc.Fields() for i := 0; i < fields.Len(); i++ { f := fields.Get(i) - fieldMap[string(f.Name())] = newFieldDescription(f) + fd := newFieldDescription(f, pbdb.jsonFieldNames) + fieldMap[fd.Name()] = fd + if pbdb.jsonFieldNames { + jsonFieldMap[fd.JSONName()] = fd + } } return &TypeDescription{ - typeName: typeName, - desc: desc, - msgType: msgType, - fieldMap: fieldMap, - extensions: extensions, - reflectType: reflectTypeOf(msgZero), - zeroMsg: zeroValueOf(msgZero), + typeName: typeName, + desc: desc, + msgType: msgType, + fieldMap: fieldMap, + jsonFieldMap: jsonFieldMap, + extensions: pbdb.extensions, + reflectType: reflectTypeOf(msgZero), + zeroMsg: zeroValueOf(msgZero), + jsonFieldNames: pbdb.jsonFieldNames, } } // TypeDescription is a collection of type metadata relevant to expression // checking and evaluation. type TypeDescription struct { - typeName string - desc protoreflect.MessageDescriptor - msgType protoreflect.MessageType - fieldMap map[string]*FieldDescription - extensions extensionMap - reflectType reflect.Type - zeroMsg proto.Message + typeName string + desc protoreflect.MessageDescriptor + msgType protoreflect.MessageType + fieldMap map[string]*FieldDescription + jsonFieldMap map[string]*FieldDescription + extensions extensionMap + reflectType reflect.Type + zeroMsg proto.Message + // jsonFieldNames indicates if the type's fields are accessible via their JSON names. + jsonFieldNames bool } // Copy copies the type description with updated references to the Db. func (td *TypeDescription) Copy(pbdb *Db) *TypeDescription { return &TypeDescription{ - typeName: td.typeName, - desc: td.desc, - msgType: td.msgType, - fieldMap: td.fieldMap, - extensions: pbdb.extensions, - reflectType: td.reflectType, - zeroMsg: td.zeroMsg, + typeName: td.typeName, + desc: td.desc, + msgType: td.msgType, + fieldMap: td.fieldMap, + jsonFieldMap: td.jsonFieldMap, + extensions: pbdb.extensions, + reflectType: td.reflectType, + zeroMsg: td.zeroMsg, + jsonFieldNames: td.jsonFieldNames, } } // FieldMap returns a string field name to FieldDescription map. func (td *TypeDescription) FieldMap() map[string]*FieldDescription { + if td.jsonFieldNames { + return td.jsonFieldMap + } return td.fieldMap } // FieldByName returns (FieldDescription, true) if the field name is declared within the type. func (td *TypeDescription) FieldByName(name string) (*FieldDescription, bool) { + if td.jsonFieldNames { + fd, found := td.jsonFieldMap[name] + if found { + return fd, true + } + } + fd, found := td.fieldMap[name] if found { return fd, true } + extFieldMap, found := td.extensions[td.typeName] - if !found { - return nil, false + if found { + fd, found = extFieldMap[name] + return fd, found } - fd, found = extFieldMap[name] - return fd, found + + return nil, false } // MaybeUnwrap accepts a proto message as input and unwraps it to a primitive CEL type if possible. @@ -132,7 +156,7 @@ func (td *TypeDescription) Zero() proto.Message { } // newFieldDescription creates a new field description from a protoreflect.FieldDescriptor. -func newFieldDescription(fieldDesc protoreflect.FieldDescriptor) *FieldDescription { +func newFieldDescription(fieldDesc protoreflect.FieldDescriptor, jsonFieldNames bool) *FieldDescription { var reflectType reflect.Type var zeroMsg proto.Message switch fieldDesc.Kind() { @@ -168,15 +192,16 @@ func newFieldDescription(fieldDesc protoreflect.FieldDescriptor) *FieldDescripti } var keyType, valType *FieldDescription if fieldDesc.IsMap() { - keyType = newFieldDescription(fieldDesc.MapKey()) - valType = newFieldDescription(fieldDesc.MapValue()) + keyType = newFieldDescription(fieldDesc.MapKey(), jsonFieldNames) + valType = newFieldDescription(fieldDesc.MapValue(), jsonFieldNames) } return &FieldDescription{ - desc: fieldDesc, - KeyType: keyType, - ValueType: valType, - reflectType: reflectType, - zeroMsg: zeroValueOf(zeroMsg), + desc: fieldDesc, + KeyType: keyType, + ValueType: valType, + reflectType: reflectType, + zeroMsg: zeroValueOf(zeroMsg), + jsonFieldName: jsonFieldNames, } } @@ -187,9 +212,10 @@ type FieldDescription struct { // ValueType holds the value FieldDescription for map fields. ValueType *FieldDescription - desc protoreflect.FieldDescriptor - reflectType reflect.Type - zeroMsg proto.Message + desc protoreflect.FieldDescriptor + reflectType reflect.Type + zeroMsg proto.Message + jsonFieldName bool } // CheckedType returns the type-definition used at type-check time. @@ -218,6 +244,14 @@ func (fd *FieldDescription) Descriptor() protoreflect.FieldDescriptor { return fd.desc } +// Documentation returns the documentation for the field. +func (fd *FieldDescription) Documentation() string { + if parentFile := fd.desc.ParentFile(); parentFile != nil { + return parentFile.SourceLocations().ByDescriptor(fd.desc).LeadingComments + } + return "" +} + // IsSet returns whether the field is set on the target value, per the proto presence conventions // of proto2 or proto3 accordingly. // @@ -321,11 +355,20 @@ func (fd *FieldDescription) MaybeUnwrapDynamic(msg protoreflect.Message) (any, b return unwrapDynamic(fd, msg) } -// Name returns the CamelCase name of the field within the proto-based struct. +// Name returns the snake_case name of the field within the proto-based struct. func (fd *FieldDescription) Name() string { return string(fd.desc.Name()) } +// JSONName returns the JSON name of the field, if present. +func (fd *FieldDescription) JSONName() string { + jsonName := fd.desc.JSONName() + if len(jsonName) != 0 { + return jsonName + } + return string(fd.desc.Name()) +} + // ProtoKind returns the protobuf reflected kind of the field. func (fd *FieldDescription) ProtoKind() protoreflect.Kind { return fd.desc.Kind() @@ -472,7 +515,7 @@ func unwrap(desc description, msg proto.Message) (any, bool, error) { } return v.GetValue(), true, nil } - return msg, false, nil + return unwrapDynamic(desc, msg.ProtoReflect()) } // unwrapDynamic unwraps a reflected protobuf Message value. diff --git a/vendor/github.com/google/cel-go/common/types/provider.go b/vendor/github.com/google/cel-go/common/types/provider.go index 936a4e28b..2321828d8 100644 --- a/vendor/github.com/google/cel-go/common/types/provider.go +++ b/vendor/github.com/google/cel-go/common/types/provider.go @@ -16,7 +16,9 @@ package types import ( "fmt" + "maps" "reflect" + "sync/atomic" "time" "google.golang.org/protobuf/proto" @@ -54,11 +56,11 @@ type Provider interface { // Returns false if not found. FindStructType(structType string) (*Type, bool) - // FindStructFieldNames returns thet field names associated with the type, if the type + // FindStructFieldNames returns the field names associated with the type, if the type // is found. FindStructFieldNames(structType string) ([]string, bool) - // FieldStructFieldType returns the field type for a checked type value. Returns + // FindStructFieldType returns the field type for a checked type value. Returns // false if the field could not be found. FindStructFieldType(structType, fieldName string) (*FieldType, bool) @@ -81,23 +83,75 @@ type FieldType struct { // GetFrom retrieves the field value on the input object, if set. GetFrom ref.FieldGetter + + // IsJSONField + IsJSONField bool } // Registry provides type information for a set of registered types. type Registry struct { - revTypeMap map[string]*Type - pbdb *pb.Db + revTypeMap map[string]*Type + structTypes map[string]StructTypeDescriptor + reflectTypes map[reflect.Type]StructTypeDescriptor + shared atomic.Bool + pbdb *pb.Db + provider Provider + adapter Adapter + nativeOptions NativeTypeOptions +} + +// NewRegistry accepts a list of proto message instances, ref.Type instances, or RegistryOption +// functions and returns a type provider. +func NewRegistry(types ...any) (*Registry, error) { + r, err := NewProtoRegistry() + if err != nil { + return nil, err + } + if err := registerTypeItems(r, types...); err != nil { + return nil, err + } + return r, nil } -// NewRegistry accepts a list of proto message instances and returns a type -// provider which can create new instances of the provided message or any -// message that proto depends upon in its FileDescriptor. -func NewRegistry(types ...proto.Message) (*Registry, error) { - p := &Registry{ - revTypeMap: make(map[string]*Type), - pbdb: pb.NewDb(), +// RegistryOption configures the behavior of the registry. +type RegistryOption func(r *Registry) (*Registry, error) + +// JSONFieldNames configures JSON field name support within the protobuf types in the registry. +func JSONFieldNames(enabled bool) RegistryOption { + return func(r *Registry) (*Registry, error) { + err := r.WithJSONFieldNames(enabled) + return r, err } - err := p.RegisterType( +} + +// ProtoTypeDefs creates a RegistryOption which registers the individual proto messages with the registry. +func ProtoTypeDefs(types ...proto.Message) RegistryOption { + return func(r *Registry) (*Registry, error) { + for _, msgType := range types { + err := r.RegisterMessage(msgType) + if err != nil { + return nil, err + } + } + return r, nil + } +} + +// Types creates a RegistryOption which registers individual custom type references or descriptors with the registry. +func Types(types ...ref.Type) RegistryOption { + return func(r *Registry) (*Registry, error) { + err := r.RegisterType(types...) + if err != nil { + return nil, err + } + return r, nil + } +} + +// NewProtoRegistry creates a proto-based registry with a set of configurable options. +func NewProtoRegistry(opts ...RegistryOption) (*Registry, error) { + r := NewEmptyRegistry() + err := r.RegisterType( BoolType, BytesType, DoubleType, @@ -114,45 +168,115 @@ func NewRegistry(types ...proto.Message) (*Registry, error) { return nil, err } // This block ensures that the well-known protobuf types are registered by default. - for _, fd := range p.pbdb.FileDescriptions() { - err = p.registerAllTypes(fd) + for _, fd := range r.pbdb.FileDescriptions() { + err = r.registerAllTypes(fd) if err != nil { return nil, err } } - for _, msgType := range types { - err = p.RegisterMessage(msgType) + for _, opt := range opts { + r, err = opt(r) if err != nil { return nil, err } } - return p, nil + return r, nil } // NewEmptyRegistry returns a registry which is completely unconfigured. func NewEmptyRegistry() *Registry { return &Registry{ - revTypeMap: make(map[string]*Type), - pbdb: pb.NewDb(), + revTypeMap: make(map[string]*Type), + structTypes: make(map[string]StructTypeDescriptor), + reflectTypes: make(map[reflect.Type]StructTypeDescriptor), + pbdb: pb.NewDb(), + } +} + +// ComposeTypes accepts a provider, adapter, and a list of types (ref.Type, proto.Message, protoreflect.FileDescriptor, or RegistryOption) +// and either: +// - Determines the provider and adapter are the same instance and a *Registry and registers the listed types via RegisterType or +// one of the other registration methods as appropriate. +// - Determines the provider and adapter are not the same, or not a *Registry and creates a new composed *Registry which references +// the new type information first and then proxies to the underlying provider and adapter methods as appropriate. +func ComposeTypes(provider Provider, adapter Adapter, types ...any) (Provider, Adapter, error) { + reg, isReg := provider.(*Registry) + aReg, isAdapterReg := adapter.(*Registry) + if isReg && isAdapterReg && reg == aReg { + if err := registerTypeItems(reg, types...); err != nil { + return nil, nil, err + } + return reg, reg, nil + } + + composedReg, err := NewRegistry(types...) + if err != nil { + return nil, nil, err } + composedReg.provider = provider + composedReg.adapter = adapter + return composedReg, composedReg, nil } // Copy copies the current state of the registry into its own memory space. func (p *Registry) Copy() *Registry { - copy := &Registry{ - revTypeMap: make(map[string]*Type), - pbdb: p.pbdb.Copy(), + if p == nil { + return nil + } + p.shared.Store(true) + cpy := &Registry{ + revTypeMap: p.revTypeMap, + structTypes: p.structTypes, + reflectTypes: p.reflectTypes, + nativeOptions: p.nativeOptions, + pbdb: p.pbdb, + provider: p.provider, + adapter: p.adapter, + } + cpy.shared.Store(true) + return cpy +} + +func (p *Registry) ensureMutable() { + if p.shared.Load() { + p.revTypeMap = maps.Clone(p.revTypeMap) + p.structTypes = maps.Clone(p.structTypes) + p.reflectTypes = maps.Clone(p.reflectTypes) + p.pbdb = p.pbdb.Copy() + p.shared.Store(false) + } +} + +// JSONFieldNames returns whether json field names are enabled in this registry. +func (p *Registry) JSONFieldNames() bool { + return p.pbdb.JSONFieldNames() +} + +// WithJSONFieldNames configures the registry with the JSON field name support enabled or disabled. +func (p *Registry) WithJSONFieldNames(enabled bool) error { + if enabled == p.pbdb.JSONFieldNames() { + return nil } - for k, v := range p.revTypeMap { - copy.revTypeMap[k] = v + p.ensureMutable() + newDB := pb.NewDb(pb.JSONFieldNames(enabled)) + files := p.pbdb.FileDescriptions() + for _, fd := range files { + _, err := newDB.RegisterDescriptor(fd.FileDescriptor()) + if err != nil { + return err + } } - return copy + p.pbdb = newDB + return nil } // EnumValue returns the numeric value of the given enum value name. func (p *Registry) EnumValue(enumName string) ref.Val { enumVal, found := p.pbdb.DescribeEnum(enumName) if !found { + if p.provider != nil { + return p.provider.EnumValue(enumName) + } return NewErr("unknown enum name '%s'", enumName) } return Int(enumVal.Value()) @@ -163,52 +287,98 @@ func (p *Registry) EnumValue(enumName string) ref.Val { // // Deprecated: use FindStructFieldType func (p *Registry) FindFieldType(structType, fieldName string) (*ref.FieldType, bool) { - msgType, found := p.pbdb.DescribeType(structType) - if !found { - return nil, false + structType = sanitizeStructTypeName(structType) + if st, found := p.structTypes[structType]; found { + if ft, found := st.FindFieldType(fieldName); found { + exprType, err := TypeToExprType(ft.Type) + if err != nil { + return nil, false + } + return makeRefFieldType(exprType, ft.IsSet, ft.GetFrom, ft.IsJSONField), true + } } - field, found := msgType.FieldByName(fieldName) - if !found { - return nil, false + if msgType, found := p.pbdb.DescribeType(structType); found { + if field, found := msgType.FieldByName(fieldName); found { + return makeRefFieldType(field.CheckedType(), field.IsSet, field.GetFrom, p.pbdb.JSONFieldNames() && fieldName == field.JSONName()), true + } } - return &ref.FieldType{ - Type: field.CheckedType(), - IsSet: field.IsSet, - GetFrom: field.GetFrom}, true + if p.provider != nil { + if ft, ok := p.provider.FindStructFieldType(structType, fieldName); ok && ft != nil { + exprType, err := TypeToExprType(ft.Type) + if err != nil { + return nil, false + } + return makeRefFieldType(exprType, ft.IsSet, ft.GetFrom, ft.IsJSONField), true + } + } + return nil, false } // FindStructFieldNames returns the set of field names for the given struct type, // if the type exists in the registry. func (p *Registry) FindStructFieldNames(structType string) ([]string, bool) { - msgType, found := p.pbdb.DescribeType(structType) - if !found { - return []string{}, false + structType = sanitizeStructTypeName(structType) + if st, found := p.structTypes[structType]; found { + return st.FieldNames(), true } - fieldMap := msgType.FieldMap() - fields := make([]string, len(fieldMap)) - idx := 0 - for f := range fieldMap { - fields[idx] = f - idx++ + if msgType, found := p.pbdb.DescribeType(structType); found { + fieldMap := msgType.FieldMap() + fields := make([]string, len(fieldMap)) + idx := 0 + for f := range fieldMap { + fields[idx] = f + idx++ + } + return fields, true } - return fields, true + if p.provider != nil { + return p.provider.FindStructFieldNames(structType) + } + return []string{}, false } // FindStructFieldType returns the field type for a checked type value. Returns // false if the field could not be found. func (p *Registry) FindStructFieldType(structType, fieldName string) (*FieldType, bool) { - msgType, found := p.pbdb.DescribeType(structType) - if !found { - return nil, false + structType = sanitizeStructTypeName(structType) + if st, found := p.structTypes[structType]; found { + if ft, found := st.FindFieldType(fieldName); found { + return ft, true + } } - field, found := msgType.FieldByName(fieldName) - if !found { - return nil, false + if msgType, found := p.pbdb.DescribeType(structType); found { + if field, found := msgType.FieldByName(fieldName); found { + return &FieldType{ + Type: fieldDescToCELType(field), + IsSet: field.IsSet, + GetFrom: field.GetFrom, + IsJSONField: p.pbdb.JSONFieldNames() && fieldName == field.JSONName(), + }, true + } + } + if p.provider != nil { + return p.provider.FindStructFieldType(structType, fieldName) + } + return nil, false +} + +// FindStructFieldDescription returns documentation for a field if available. +// Returns false if the field could not be found. +func (p *Registry) FindStructFieldDescription(structType, fieldName string) (string, bool) { + structType = sanitizeStructTypeName(structType) + if msgType, found := p.pbdb.DescribeType(structType); found { + if field, found := msgType.FieldByName(fieldName); found { + return field.Documentation(), true + } + } + if p.provider != nil { + if pd, ok := p.provider.(interface { + FindStructFieldDescription(string, string) (string, bool) + }); ok { + return pd.FindStructFieldDescription(structType, fieldName) + } } - return &FieldType{ - Type: fieldDescToCELType(field), - IsSet: field.IsSet, - GetFrom: field.GetFrom}, true + return "", false } // FindIdent takes a qualified identifier name and returns a ref.Val if one exists. @@ -219,6 +389,9 @@ func (p *Registry) FindIdent(identName string) (ref.Val, bool) { if enumVal, found := p.pbdb.DescribeEnum(identName); found { return Int(enumVal.Value()), true } + if p.provider != nil { + return p.provider.FindIdent(identName) + } return nil, false } @@ -226,17 +399,19 @@ func (p *Registry) FindIdent(identName string) (ref.Val, bool) { // // Deprecated: use FindStructType func (p *Registry) FindType(structType string) (*exprpb.Type, bool) { - if _, found := p.pbdb.DescribeType(structType); !found { - return nil, false + structType = sanitizeStructTypeName(structType) + if p.hasStructType(structType) { + return makeExprMessageType(structType), true } - if structType != "" && structType[0] == '.' { - structType = structType[1:] + if p.provider != nil { + if tp, ok := p.provider.(ref.TypeProvider); ok { + return tp.FindType(structType) + } + if _, ok := p.provider.FindStructType(structType); ok { + return makeExprMessageType(structType), true + } } - return &exprpb.Type{ - TypeKind: &exprpb.Type_Type{ - Type: &exprpb.Type{ - TypeKind: &exprpb.Type_MessageType{ - MessageType: structType}}}}, true + return nil, false } // FindStructType returns the Type give a qualified type name. @@ -247,13 +422,14 @@ func (p *Registry) FindType(structType string) (*exprpb.Type, bool) { // // Returns false if not found. func (p *Registry) FindStructType(structType string) (*Type, bool) { - if _, found := p.pbdb.DescribeType(structType); !found { - return nil, false + structType = sanitizeStructTypeName(structType) + if p.hasStructType(structType) { + return NewTypeTypeWithParam(NewObjectType(structType)), true } - if structType != "" && structType[0] == '.' { - structType = structType[1:] + if p.provider != nil { + return p.provider.FindStructType(structType) } - return NewTypeTypeWithParam(NewObjectType(structType)), true + return nil, false } // NewValue creates a new type value from a qualified name and map of field @@ -263,14 +439,20 @@ func (p *Registry) FindStructType(structType string) (*Type, bool) { // to convert the Val to the field's native type. If an error occurs during // conversion, the NewValue will be a types.Err. func (p *Registry) NewValue(structType string, fields map[string]ref.Val) ref.Val { + structType = sanitizeStructTypeName(structType) + if st, found := p.structTypes[structType]; found { + return st.NewValue(p, fields) + } td, found := p.pbdb.DescribeType(structType) if !found { + if p.provider != nil { + return p.provider.NewValue(structType, fields) + } return NewErr("unknown type '%s'", structType) } msg := td.New() - fieldMap := td.FieldMap() for name, value := range fields { - field, found := fieldMap[name] + field, found := td.FieldByName(name) if !found { return NewErr("no such field: %s", name) } @@ -284,6 +466,7 @@ func (p *Registry) NewValue(structType string, fields map[string]ref.Val) ref.Va // RegisterDescriptor registers the contents of a protocol buffer `FileDescriptor`. func (p *Registry) RegisterDescriptor(fileDesc protoreflect.FileDescriptor) error { + p.ensureMutable() fd, err := p.pbdb.RegisterDescriptor(fileDesc) if err != nil { return err @@ -293,6 +476,7 @@ func (p *Registry) RegisterDescriptor(fileDesc protoreflect.FileDescriptor) erro // RegisterMessage registers a protocol buffer message and its dependencies. func (p *Registry) RegisterMessage(message proto.Message) error { + p.ensureMutable() fd, err := p.pbdb.RegisterMessage(message) if err != nil { return err @@ -311,338 +495,418 @@ func (p *Registry) RegisterMessage(message proto.Message) error { // to CEL, even when they're not based on protobuf types. func (p *Registry) RegisterType(types ...ref.Type) error { for _, t := range types { - celType := maybeForeignType(t) existing, found := p.revTypeMap[t.TypeName()] - if !found { - p.revTypeMap[t.TypeName()] = celType + celType := maybeForeignType(t) + if found { + if !existing.IsEquivalentType(celType) { + return fmt.Errorf("type registration conflict. found: %v, input: %v", existing, celType) + } + if existing.traitMask != celType.traitMask { + return fmt.Errorf( + "type registered with conflicting traits: %v with traits %v, input: %v", + existing.TypeName(), existing.traitMask, celType.traitMask) + } continue } - if !existing.IsEquivalentType(celType) { - return fmt.Errorf("type registration conflict. found: %v, input: %v", existing, celType) - } - if existing.traitMask != celType.traitMask { - return fmt.Errorf( - "type registered with conflicting traits: %v with traits %v, input: %v", - existing.TypeName(), existing.traitMask, celType.traitMask) + + p.ensureMutable() + typeName := t.TypeName() + p.revTypeMap[typeName] = celType + if st, ok := t.(StructTypeDescriptor); ok { + // Conflicts are gated above so if we see a struct here, it's safe to register. + p.structTypes[typeName] = st + if rt := st.ReflectType(); rt != nil { + p.reflectTypes[rt] = st + if rt.Kind() == reflect.Ptr { + p.reflectTypes[rt.Elem()] = st + } else { + p.reflectTypes[reflect.PointerTo(rt)] = st + } + } } } return nil } -// NativeToValue converts various "native" types to ref.Val with this specific implementation -// providing support for custom proto-based types. -// -// This method should be the inverse of ref.Val.ConvertToNative. -func (p *Registry) NativeToValue(value any) ref.Val { - if val, found := nativeToValue(p, value); found { - return val - } - switch v := value.(type) { - case proto.Message: - typeName := string(v.ProtoReflect().Descriptor().FullName()) - td, found := p.pbdb.DescribeType(typeName) - if !found { - return NewErr("unknown type: '%s'", typeName) - } - unwrapped, isUnwrapped, err := td.MaybeUnwrap(v) - if err != nil { - return UnsupportedRefValConversionErr(v) - } - if isUnwrapped { - return p.NativeToValue(unwrapped) - } - typeVal, found := p.FindIdent(typeName) - if !found { - return NewErr("unknown type: '%s'", typeName) - } - return NewObject(p, td, typeVal, v) - case *pb.Map: - return NewProtoMap(p, v) - case protoreflect.List: - return NewProtoList(p, v) - case protoreflect.Message: - return p.NativeToValue(v.Interface()) - case protoreflect.Value: - return p.NativeToValue(v.Interface()) +// RegisterNativeType creates nativeType instances for the given reflect.Type and registers them. +func (p *Registry) RegisterNativeType(refType reflect.Type) error { + result, err := newNativeTypes(refType, p.nativeOptions.fieldNameHandler) + if err != nil { + return err } - return UnsupportedRefValConversionErr(value) -} - -func (p *Registry) registerAllTypes(fd *pb.FileDescription) error { - for _, typeName := range fd.GetTypeNames() { - // skip well-known type names since they're automatically sanitized - // during NewObjectType() calls. - if _, found := checkedWellKnowns[typeName]; found { - continue - } - err := p.RegisterType(NewObjectTypeValue(typeName)) - if err != nil { + for _, nt := range result { + if err := p.RegisterType(nt); err != nil { return err } } return nil } -func fieldDescToCELType(field *pb.FieldDescription) *Type { - if field.IsMap() { - return NewMapType( - singularFieldDescToCELType(field.KeyType), - singularFieldDescToCELType(field.ValueType)) - } - if field.IsList() { - return NewListType(singularFieldDescToCELType(field)) - } - return singularFieldDescToCELType(field) -} - -func singularFieldDescToCELType(field *pb.FieldDescription) *Type { - if field.IsMessage() { - return NewObjectType(string(field.Descriptor().Message().FullName())) +func (p *Registry) findStructDescriptorByReflectType(rt reflect.Type) (StructTypeDescriptor, bool) { + if rt == nil { + return nil, false } - if field.IsEnum() { - return IntType + if st, found := p.reflectTypes[rt]; found { + return st, true } - return ProtoCELPrimitives[field.ProtoKind()] -} - -// defaultTypeAdapter converts go native types to CEL values. -type defaultTypeAdapter struct{} - -var ( - // DefaultTypeAdapter adapts canonical CEL types from their equivalent Go values. - DefaultTypeAdapter = &defaultTypeAdapter{} -) - -// NativeToValue implements the ref.TypeAdapter interface. -func (a *defaultTypeAdapter) NativeToValue(value any) ref.Val { - if val, found := nativeToValue(a, value); found { - return val + if rt.Kind() == reflect.Ptr { + if st, found := p.reflectTypes[rt.Elem()]; found { + return st, true + } + } else { + if st, found := p.reflectTypes[reflect.PointerTo(rt)]; found { + return st, true + } } - return UnsupportedRefValConversionErr(value) + return nil, false } -// nativeToValue returns the converted (ref.Val, true) of a conversion is found, -// otherwise (nil, false) -func nativeToValue(a Adapter, value any) (ref.Val, bool) { +// NativeToValue converts various "native" types to ref.Val with this specific implementation +// providing support for custom proto-based types. +// +// This method should be the inverse of ref.Val.ConvertToNative. +func (p *Registry) NativeToValue(value any) ref.Val { switch v := value.(type) { case nil: - return NullValue, true + return NullValue case *Bool: if v != nil { - return *v, true + return *v } case *Bytes: if v != nil { - return *v, true + return *v } case *Double: if v != nil { - return *v, true + return *v } case *Int: if v != nil { - return *v, true + return *v } case *String: if v != nil { - return *v, true + return *v } case *Uint: if v != nil { - return *v, true + return *v } + case ref.Val: + return v case bool: - return Bool(v), true + return Bool(v) case int: - return Int(v), true + return Int(v) case int32: - return Int(v), true + return Int(v) case int64: - return Int(v), true + return Int(v) case uint: - return Uint(v), true + return Uint(v) case uint32: - return Uint(v), true + return Uint(v) case uint64: - return Uint(v), true + return Uint(v) case float32: - return Double(v), true + return Double(v) case float64: - return Double(v), true + return Double(v) case string: - return String(v), true + return String(v) case *dpb.Duration: - return Duration{Duration: v.AsDuration()}, true + return Duration{Duration: v.AsDuration()} case time.Duration: - return Duration{Duration: v}, true + return Duration{Duration: v} case *tpb.Timestamp: - return Timestamp{Time: v.AsTime()}, true + return Timestamp{Time: v.AsTime()} case time.Time: - return Timestamp{Time: v}, true + return Timestamp{Time: v} case *bool: if v != nil { - return Bool(*v), true + return Bool(*v) } case *float32: if v != nil { - return Double(*v), true + return Double(*v) } case *float64: if v != nil { - return Double(*v), true + return Double(*v) } case *int: if v != nil { - return Int(*v), true + return Int(*v) } case *int32: if v != nil { - return Int(*v), true + return Int(*v) } case *int64: if v != nil { - return Int(*v), true + return Int(*v) } case *string: if v != nil { - return String(*v), true + return String(*v) } case *uint: if v != nil { - return Uint(*v), true + return Uint(*v) } case *uint32: if v != nil { - return Uint(*v), true + return Uint(*v) } case *uint64: if v != nil { - return Uint(*v), true + return Uint(*v) } case []byte: - return Bytes(v), true + return Bytes(v) // specializations for common lists types. case []string: - return NewStringList(a, v), true + return NewStringList(p, v) case []ref.Val: - return NewRefValList(a, v), true + return NewRefValList(p, v) // specializations for common map types. case map[string]string: - return NewStringStringMap(a, v), true + return NewStringStringMap(p, v) case map[string]any: - return NewStringInterfaceMap(a, v), true + return NewStringInterfaceMap(p, v) case map[ref.Val]ref.Val: - return NewRefValMap(a, v), true + return NewRefValMap(p, v) // additional specializations may be added upon request / need. case *anypb.Any: if v == nil { - return UnsupportedRefValConversionErr(v), true + return UnsupportedRefValConversionErr(v) } unpackedAny, err := v.UnmarshalNew() if err != nil { - return NewErr("anypb.UnmarshalNew() failed for type %q: %v", v.GetTypeUrl(), err), true + return NewErr("anypb.UnmarshalNew() failed for type %q: %v", v.GetTypeUrl(), err) } - return a.NativeToValue(unpackedAny), true + return p.NativeToValue(unpackedAny) case *structpb.NullValue, structpb.NullValue: - return NullValue, true + return NullValue case *structpb.ListValue: - return NewJSONList(a, v), true + return NewJSONList(p, v) case *structpb.Struct: - return NewJSONStruct(a, v), true - case ref.Val: - return v, true + return NewJSONStruct(p, v) case protoreflect.EnumNumber: - return Int(v), true + return Int(v) case proto.Message: if v == nil { - return UnsupportedRefValConversionErr(v), true + return UnsupportedRefValConversionErr(v) } typeName := string(v.ProtoReflect().Descriptor().FullName()) - td, found := pb.DefaultDb.DescribeType(typeName) + pbdb := p.pbdb + if pbdb == nil { + pbdb = pb.DefaultDb + } + td, found := pbdb.DescribeType(typeName) if !found { - return nil, false + if p.adapter != nil { + return p.adapter.NativeToValue(value) + } + return NewErr("unknown type: '%s'", typeName) } - val, unwrapped, err := td.MaybeUnwrap(v) + unwrapped, isUnwrapped, err := td.MaybeUnwrap(v) if err != nil { - return UnsupportedRefValConversionErr(v), true + return UnsupportedRefValConversionErr(v) } - if !unwrapped { - return nil, false + if isUnwrapped { + return p.NativeToValue(unwrapped) } - return a.NativeToValue(val), true - // Note: dynamicpb.Message implements the proto.Message _and_ protoreflect.Message interfaces - // which means that this case must appear after handling a proto.Message type. + typeVal, found := p.FindIdent(typeName) + if !found { + return NewErr("unknown type: '%s'", typeName) + } + return NewObject(p, td, typeVal, v) + case *pb.Map: + return NewProtoMap(p, v) + case protoreflect.List: + return NewProtoList(p, v) case protoreflect.Message: - return a.NativeToValue(v.Interface()), true + return p.NativeToValue(v.Interface()) + case protoreflect.Value: + return p.NativeToValue(v.Interface()) default: - refValue := reflect.ValueOf(v) - if refValue.Kind() == reflect.Ptr { - if refValue.IsNil() { - return UnsupportedRefValConversionErr(v), true + rt := reflect.TypeOf(value) + if len(p.reflectTypes) > 0 { + if st, found := p.findStructDescriptorByReflectType(rt); found { + return st.Adapt(p, value) } - refValue = refValue.Elem() } - refKind := refValue.Kind() - switch refKind { + refVal := reflect.ValueOf(v) + if refVal.Kind() == reflect.Ptr { + if refVal.IsNil() { + break + } + refVal = refVal.Elem() + } + switch refVal.Kind() { case reflect.Array, reflect.Slice: - if refValue.Type().Elem() == reflect.TypeOf(byte(0)) { - if refValue.CanAddr() { - return Bytes(refValue.Bytes()), true + if refVal.Type().Elem() == reflect.TypeOf(byte(0)) { + if refVal.CanAddr() { + return Bytes(refVal.Bytes()) } - tmp := reflect.New(refValue.Type()) - tmp.Elem().Set(refValue) - return Bytes(tmp.Elem().Bytes()), true + tmp := reflect.New(refVal.Type()) + tmp.Elem().Set(refVal) + return Bytes(tmp.Elem().Bytes()) } - return NewDynamicList(a, v), true + return NewDynamicList(p, v) case reflect.Map: - return NewDynamicMap(a, v), true - // type aliases of primitive types cannot be asserted as that type, but rather need - // to be downcast to int32 before being converted to a CEL representation. + return NewDynamicMap(p, v) case reflect.Bool: - boolTupe := reflect.TypeOf(false) - return Bool(refValue.Convert(boolTupe).Interface().(bool)), true - case reflect.Int: - intType := reflect.TypeOf(int(0)) - return Int(refValue.Convert(intType).Interface().(int)), true - case reflect.Int8: - intType := reflect.TypeOf(int8(0)) - return Int(refValue.Convert(intType).Interface().(int8)), true - case reflect.Int16: - intType := reflect.TypeOf(int16(0)) - return Int(refValue.Convert(intType).Interface().(int16)), true - case reflect.Int32: - intType := reflect.TypeOf(int32(0)) - return Int(refValue.Convert(intType).Interface().(int32)), true - case reflect.Int64: - intType := reflect.TypeOf(int64(0)) - return Int(refValue.Convert(intType).Interface().(int64)), true - case reflect.Uint: - uintType := reflect.TypeOf(uint(0)) - return Uint(refValue.Convert(uintType).Interface().(uint)), true - case reflect.Uint8: - uintType := reflect.TypeOf(uint8(0)) - return Uint(refValue.Convert(uintType).Interface().(uint8)), true - case reflect.Uint16: - uintType := reflect.TypeOf(uint16(0)) - return Uint(refValue.Convert(uintType).Interface().(uint16)), true - case reflect.Uint32: - uintType := reflect.TypeOf(uint32(0)) - return Uint(refValue.Convert(uintType).Interface().(uint32)), true - case reflect.Uint64: - uintType := reflect.TypeOf(uint64(0)) - return Uint(refValue.Convert(uintType).Interface().(uint64)), true - case reflect.Float32: - doubleType := reflect.TypeOf(float32(0)) - return Double(refValue.Convert(doubleType).Interface().(float32)), true - case reflect.Float64: - doubleType := reflect.TypeOf(float64(0)) - return Double(refValue.Convert(doubleType).Interface().(float64)), true + return Bool(refVal.Bool()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return Int(refVal.Int()) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return Uint(refVal.Uint()) + case reflect.Float32, reflect.Float64: + return Double(refVal.Float()) case reflect.String: - stringType := reflect.TypeOf("") - return String(refValue.Convert(stringType).Interface().(string)), true + return String(refVal.String()) } } - return nil, false + if p.adapter != nil { + return p.adapter.NativeToValue(value) + } + return UnsupportedRefValConversionErr(value) +} + +func (p *Registry) registerAllTypes(fd *pb.FileDescription) error { + for _, typeName := range fd.GetTypeNames() { + // skip well-known type names since they're automatically sanitized + // during NewObjectType() calls. + if _, found := checkedWellKnowns[typeName]; found { + continue + } + err := p.RegisterType(NewObjectTypeValue(typeName)) + if err != nil { + return err + } + } + return nil +} + +func (p *Registry) hasStructType(structType string) bool { + if _, found := p.structTypes[structType]; found { + return true + } + _, found := p.pbdb.DescribeType(structType) + return found +} + +func sanitizeStructTypeName(structType string) string { + if len(structType) > 0 && structType[0] == '.' { + return structType[1:] + } + return structType +} + +func registerTypeItems(r *Registry, types ...any) error { + opts := make([]any, 0, len(types)) + items := make([]any, 0, len(types)) + for _, t := range types { + switch t.(type) { + case NativeTypeOption: + opts = append(opts, t) + default: + items = append(items, t) + } + } + for _, opt := range opts { + if err := registerTypeItem(r, opt); err != nil { + return err + } + } + for _, item := range items { + if err := registerTypeItem(r, item); err != nil { + return err + } + } + return nil +} + +func registerTypeItem(r *Registry, t any) error { + switch v := t.(type) { + case proto.Message: + return r.RegisterMessage(v) + case protoreflect.FileDescriptor: + return r.RegisterDescriptor(v) + case ref.Type: + return r.RegisterType(v) + case reflect.Type: + return r.RegisterNativeType(v) + case reflect.Value: + return r.RegisterNativeType(v.Type()) + case NativeTypeOption: + return v(&r.nativeOptions) + case RegistryOption: + _, err := v(r) + return err + default: + return fmt.Errorf("unsupported type: %v (%T) must be reflect.Type or reflect.Value", t, t) + } +} + +func makeExprMessageType(structType string) *exprpb.Type { + return &exprpb.Type{ + TypeKind: &exprpb.Type_Type{ + Type: &exprpb.Type{ + TypeKind: &exprpb.Type_MessageType{ + MessageType: structType, + }, + }, + }, + } +} + +func makeRefFieldType(t *exprpb.Type, isSet ref.FieldTester, getFrom ref.FieldGetter, isJSONField bool) *ref.FieldType { + return &ref.FieldType{ + Type: t, + IsSet: isSet, + GetFrom: getFrom, + IsJSONField: isJSONField, + } +} + +func fieldDescToCELType(field *pb.FieldDescription) *Type { + if field.IsMap() { + return NewMapType( + singularFieldDescToCELType(field.KeyType), + singularFieldDescToCELType(field.ValueType)) + } + if field.IsList() { + return NewListType(singularFieldDescToCELType(field)) + } + return singularFieldDescToCELType(field) +} + +func singularFieldDescToCELType(field *pb.FieldDescription) *Type { + if field.IsMessage() { + return NewObjectType(string(field.Descriptor().Message().FullName())) + } + if field.IsEnum() { + return IntType + } + return ProtoCELPrimitives[field.ProtoKind()] +} + +// defaultTypeAdapter converts go native types to CEL values. +type defaultTypeAdapter struct{} + +var ( + // DefaultTypeAdapter adapts canonical CEL types from their equivalent Go values. + DefaultTypeAdapter = &defaultTypeAdapter{} + emptyRegistry = &Registry{pbdb: pb.DefaultDb} +) + +// NativeToValue implements the ref.TypeAdapter interface. +func (a *defaultTypeAdapter) NativeToValue(value any) ref.Val { + return emptyRegistry.NativeToValue(value) } func msgSetField(target protoreflect.Message, field *pb.FieldDescription, val ref.Val) error { diff --git a/vendor/github.com/google/cel-go/common/types/ref/provider.go b/vendor/github.com/google/cel-go/common/types/ref/provider.go index b9820023d..ed5ab0662 100644 --- a/vendor/github.com/google/cel-go/common/types/ref/provider.go +++ b/vendor/github.com/google/cel-go/common/types/ref/provider.go @@ -93,6 +93,9 @@ type FieldType struct { // GetFrom retrieves the field value on the input object, if set. GetFrom FieldGetter + + // IsJSONFIeld indicates that the field was accessed via its JSON name. + IsJSONField bool } // FieldTester is used to test field presence on an input object. diff --git a/vendor/github.com/google/cel-go/common/types/regex.go b/vendor/github.com/google/cel-go/common/types/regex.go new file mode 100644 index 000000000..14173eb52 --- /dev/null +++ b/vendor/github.com/google/cel-go/common/types/regex.go @@ -0,0 +1,49 @@ +// Copyright 2026 Google LLC +// +// 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 types + +import ( + "fmt" + "regexp" + "regexp/syntax" +) + +// RegexProgramSize calculates the instruction count (program plan size) of a regex pattern. +func RegexProgramSize(pattern string) (int, error) { + re, err := syntax.Parse(pattern, syntax.Perl) + if err != nil { + return 0, err + } + prog, err := syntax.Compile(re) + if err != nil { + return 0, err + } + return len(prog.Inst), nil +} + +// CompileRegexWithLimit compiles a regex pattern and verifies that its program plan size does not exceed limit. +// A limit <= 0 means unbounded. +func CompileRegexWithLimit(pattern string, limit int) (*regexp.Regexp, error) { + if limit > 0 { + sz, err := RegexProgramSize(pattern) + if err != nil { + return nil, err + } + if sz > limit { + return nil, fmt.Errorf("regex program size %d exceeds limit of %d", sz, limit) + } + } + return regexp.Compile(pattern) +} diff --git a/vendor/github.com/google/cel-go/common/types/string.go b/vendor/github.com/google/cel-go/common/types/string.go index 8aad4701c..1335903a7 100644 --- a/vendor/github.com/google/cel-go/common/types/string.go +++ b/vendor/github.com/google/cel-go/common/types/string.go @@ -72,7 +72,7 @@ func (s String) ConvertToNative(typeDesc reflect.Type) (any, error) { case anyValueType: // Primitives must be wrapped before being set on an Any field. return anypb.New(wrapperspb.String(string(s))) - case jsonValueType: + case JSONValueType: // Convert to a protobuf representation of a JSON String. return structpb.NewStringValue(string(s)), nil case stringWrapperType: @@ -122,7 +122,11 @@ func (s String) ConvertToType(typeVal ref.Type) ref.Val { return durationOf(d) } case TimestampType: - if t, err := time.Parse(time.RFC3339, s.Value().(string)); err == nil { + str := s.Value().(string) + if !isStrictRFC3339(str) { + return NewErr("invalid RFC 3339 timestamp %q", str) + } + if t, err := time.Parse(time.RFC3339, str); err == nil { if t.Unix() < minUnixTime || t.Unix() > maxUnixTime { return celErrTimestampOverflow } diff --git a/vendor/github.com/google/cel-go/common/types/struct.go b/vendor/github.com/google/cel-go/common/types/struct.go new file mode 100644 index 000000000..61740a70e --- /dev/null +++ b/vendor/github.com/google/cel-go/common/types/struct.go @@ -0,0 +1,39 @@ +// Copyright 2026 Google LLC +// +// 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 types + +import ( + "reflect" + + "github.com/google/cel-go/common/types/ref" +) + +// StructTypeDescriptor describes a CEL struct type, providing field metadata and value instantiation. +type StructTypeDescriptor interface { + // ReflectType returns the backing Go reflect.Type associated with the struct (or nil if non-reflected). + ReflectType() reflect.Type + + // FieldNames returns the list of field names defined on the struct. + FieldNames() []string + + // FindFieldType returns the field type and a boolean indicating if the field exists. + FindFieldType(fieldName string) (*FieldType, bool) + + // NewValue creates a new CEL struct value from the given map of field values. + NewValue(adapter Adapter, fields map[string]ref.Val) ref.Val + + // Adapt converts a native Go value (struct instance or pointer) to a CEL ref.Val. + Adapt(adapter Adapter, value any) ref.Val +} diff --git a/vendor/github.com/google/cel-go/common/types/timestamp.go b/vendor/github.com/google/cel-go/common/types/timestamp.go index f7be58591..aee47f053 100644 --- a/vendor/github.com/google/cel-go/common/types/timestamp.go +++ b/vendor/github.com/google/cel-go/common/types/timestamp.go @@ -17,9 +17,11 @@ package types import ( "fmt" "reflect" + "regexp" "strconv" "strings" "time" + "unicode" "github.com/google/cel-go/common/overloads" "github.com/google/cel-go/common/types/ref" @@ -52,6 +54,79 @@ const ( maxUnixTime int64 = 253402300799 ) +// strictRFC3339Pattern gates the strings accepted by the `timestamp()` overload. +// time.Parse accepts inputs that RFC 3339 forbids: a ',' fractional-second +// separator, single-digit time fields, and numeric offsets whose hours exceed +// 23 or minutes exceed 59. Those slip past unnoticed and shift the parsed +// instant, so they are rejected before time.Parse runs. Month and day are held +// to the grammar ranges 01-12 and 01-31; the remaining calendar validation +// (day-of-month vs. month, leap years) is left to time.Parse. +// +// isStrictRFC3339 is the implementation used on the conversion path; the pattern +// is retained as the reference the scan is conformance tested against. +var strictRFC3339Pattern = regexp.MustCompile( + `^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])[Tt]([01]\d|2[0-3]):[0-5]\d:([0-5]\d|60)(\.\d+)?([Zz]|[+-]([01]\d|2[0-3]):[0-5]\d)$`) + +// isStrictRFC3339 reports whether s matches strictRFC3339Pattern, hand-rolled to +// keep the conversion path off the regexp engine and its per-call cost. +func isStrictRFC3339(s string) bool { + // Shortest accepted form is "2006-01-02T15:04:05Z" (20 bytes): a 19-byte + // fixed-width date-time followed by at least a 'Z'/'z' zone. + if len(s) < 20 { + return false + } + // full-date "T" partial-time + if !isYear(s[0:4]) || !isChar(s[4], '-') || !isMonth(s[5:7]) || !isChar(s[7], '-') || !isDay(s[8:10]) || + !isChar(s[10], 't') || + !isHour(s[11:13]) || !isChar(s[13], ':') || !isMinute(s[14:16]) || !isChar(s[16], ':') || !isSecond(s[17:19]) { + return false + } + rest := s[19:] + // optional fractional seconds: "." 1*DIGIT + if rest[0] == '.' { + rest = rest[1:] + n := 0 + for n < len(rest) && isDigit(rest[n]) { + n++ + } + if n == 0 { + return false + } + rest = rest[n:] + } + // time-offset: "Z" or ("+" / "-") time-hour ":" time-minute + if len(rest) == 1 { + return isChar(rest[0], 'z') + } + if len(rest) == 6 && (rest[0] == '+' || rest[0] == '-') { + return isHour(rest[1:3]) && isChar(rest[3], ':') && isMinute(rest[4:6]) + } + return false +} + +func isDigit(c byte) bool { return c >= '0' && c <= '9' } + +// isChar reports whether got is want, case-insensitively; want must be lower case. +func isChar(got, want byte) bool { + g, w := rune(got), rune(want) + return g == w || unicode.ToLower(g) == w +} + +// inRange reports whether s is all decimal digits and its value lies in [lo, hi]. +func inRange(s string, lo, hi uint64) bool { + u, err := strconv.ParseUint(s, 10, 64) + return err == nil && u >= lo && u <= hi +} + +func isYear(s string) bool { return inRange(s, 0, 9999) } +func isMonth(s string) bool { return inRange(s, 1, 12) } +func isDay(s string) bool { return inRange(s, 1, 31) } +func isHour(s string) bool { return inRange(s, 0, 23) } +func isMinute(s string) bool { return inRange(s, 0, 59) } + +// isSecond permits 60 for a leap second. +func isSecond(s string) bool { return inRange(s, 0, 60) } + // Add implements traits.Adder.Add. func (t Timestamp) Add(other ref.Val) ref.Val { switch other.Type() { @@ -91,7 +166,7 @@ func (t Timestamp) ConvertToNative(typeDesc reflect.Type) (any, error) { case anyValueType: // Pack the underlying time as a tpb.Timestamp into an Any value. return anypb.New(tpb.New(t.Time)) - case jsonValueType: + case JSONValueType: // CEL follows the proto3 to JSON conversion which formats as an RFC 3339 encoded JSON // string. v := t.ConvertToType(StringType) @@ -293,7 +368,7 @@ func timeZone(tz ref.Val, visitor timestampVisitor) timestampVisitor { } // If the input is not the name of a timezone (for example, 'US/Central'), it should be a numerical offset from UTC - // in the format ^(+|-)(0[0-9]|1[0-4]):[0-5][0-9]$. The numerical input is parsed in terms of hours and minutes. + // in the format ^(+|-)([01]\d|2[0-3]):[0-5][0-9]$. The numerical input is parsed in terms of hours and minutes. hr, err := strconv.Atoi(string(val[0:ind])) if err != nil { return WrapErr(err) @@ -302,6 +377,12 @@ func timeZone(tz ref.Val, visitor timestampVisitor) timestampVisitor { if err != nil { return WrapErr(err) } + if hr < -23 || hr > 23 { + return WrapErr(fmt.Errorf("timezone offset hours out of range [-23, 23]: %s", val)) + } + if min < 0 || min > 59 { + return WrapErr(fmt.Errorf("timezone offset minutes out of range [0, 59]: %s", val)) + } var offset int if string(val[0]) == "-" { offset = hr*60 - min diff --git a/vendor/github.com/google/cel-go/common/types/uint.go b/vendor/github.com/google/cel-go/common/types/uint.go index a93405a13..91d5369da 100644 --- a/vendor/github.com/google/cel-go/common/types/uint.go +++ b/vendor/github.com/google/cel-go/common/types/uint.go @@ -100,7 +100,7 @@ func (i Uint) ConvertToNative(typeDesc reflect.Type) (any, error) { case anyValueType: // Primitives must be wrapped before being set on an Any field. return anypb.New(wrapperspb.UInt64(uint64(i))) - case jsonValueType: + case JSONValueType: // JSON can accurately represent 32-bit uints as floating point values. if i.isJSONSafe() { return structpb.NewNumberValue(float64(i)), nil diff --git a/vendor/github.com/google/cel-go/common/types/unknown.go b/vendor/github.com/google/cel-go/common/types/unknown.go index 9dd2b2579..f43aff18e 100644 --- a/vendor/github.com/google/cel-go/common/types/unknown.go +++ b/vendor/github.com/google/cel-go/common/types/unknown.go @@ -16,6 +16,7 @@ package types import ( "fmt" + "maps" "math" "reflect" "sort" @@ -181,6 +182,20 @@ func (u *Unknown) GetAttributeTrails(id int64) ([]*AttributeTrail, bool) { return trails, found } +// HasUnknownFunction returns whether any of the attribute trails contained within the unknown +// are unspecified. Unspecified attributes typically indicate an unresolved function call +// or operation, rather than a missing variable. +func (u *Unknown) HasUnknownFunction() bool { + for _, trails := range u.attributeTrails { + for _, t := range trails { + if t.variable == "" { + return true + } + } + } + return false +} + // Contains returns true if the input unknown is a subset of the current unknown. func (u *Unknown) Contains(other *Unknown) bool { for id, otherTrails := range other.attributeTrails { @@ -283,9 +298,7 @@ func MergeUnknowns(unk1, unk2 *Unknown) *Unknown { out := &Unknown{ attributeTrails: make(map[int64][]*AttributeTrail, len(unk1.attributeTrails)+len(unk2.attributeTrails)), } - for id, ats := range unk1.attributeTrails { - out.attributeTrails[id] = ats - } + maps.Copy(out.attributeTrails, unk1.attributeTrails) for id, ats := range unk2.attributeTrails { existing, found := out.attributeTrails[id] if !found { diff --git a/vendor/github.com/google/cel-go/ext/BUILD.bazel b/vendor/github.com/google/cel-go/ext/BUILD.bazel index ef4f4ec3d..f362fd97b 100644 --- a/vendor/github.com/google/cel-go/ext/BUILD.bazel +++ b/vendor/github.com/google/cel-go/ext/BUILD.bazel @@ -9,6 +9,7 @@ go_library( srcs = [ "bindings.go", "comprehensions.go", + "costs.go", "encoders.go", "extension_option_factory.go", "formatting.go", @@ -17,6 +18,7 @@ go_library( "lists.go", "math.go", "native.go", + "network.go", "protos.go", "regex.go", "sets.go", @@ -39,6 +41,7 @@ go_library( "//common/types/traits:go_default_library", "//interpreter:go_default_library", "//parser:go_default_library", + "@org_golang_google_protobuf//encoding/protojson:go_default_library", "@org_golang_google_protobuf//proto:go_default_library", "@org_golang_google_protobuf//reflect/protoreflect:go_default_library", "@org_golang_google_protobuf//types/known/structpb", @@ -60,6 +63,7 @@ go_test( "lists_test.go", "math_test.go", "native_test.go", + "network_test.go", "protos_test.go", "regex_test.go", "sets_test.go", diff --git a/vendor/github.com/google/cel-go/ext/README.md b/vendor/github.com/google/cel-go/ext/README.md index 41ae6a314..6133b5cbf 100644 --- a/vendor/github.com/google/cel-go/ext/README.md +++ b/vendor/github.com/google/cel-go/ext/README.md @@ -33,6 +33,8 @@ Encoding utilities for marshalling data into standardized representations. ### Base64.Decode +**Introduced in version 0 (cost support in version 1)** + Decodes base64-encoded string to bytes. This function will return an error if the string input is not @@ -47,6 +49,8 @@ Examples: ### Base64.Encode +**Introduced in version 0 (cost support in version 1)** + Encodes bytes to a base64-encoded string. base64.encode() -> @@ -55,6 +59,20 @@ Example: base64.encode(b'hello') // return 'aGVsbG8=' +### JSON.Encode + +Introduced at version: 1 + +Encodes a CEL value to a JSON string. + + json.encode() -> + +Examples: + + json.encode('hello') // return '"hello"' + json.encode([1, 'two', true]) // return '[1,"two",true]' + json.encode({'items': [1, 'two', false]}) // return '{"items":[1,"two",false]}' + ## Math Math helper macros and functions. @@ -66,6 +84,8 @@ intended; however, there is some chance for collision. ### Math.Greatest +**Introduced in version 0 (cost support in version 3)** + Returns the greatest valued number present in the arguments to the macro. Greatest is a variable argument count macro which must take at least one @@ -93,6 +113,8 @@ Examples: ### Math.Least +**Introduced in version 0 (cost support in version 3)** + Returns the least valued number present in the arguments to the macro. Least is a variable argument count macro which must take at least one @@ -931,10 +953,10 @@ type will cause a key collision. Elements in the map may optionally be filtered according to a predicate expression, where elements that satisfy the predicate are transformed. - .transformMap(indexVar, valueVar, ) - .transformMap(indexVar, valueVar, , ) - .transformMap(keyVar, valueVar, ) - .transformMap(keyVar, valueVar, , ) + .transformMapEntry(indexVar, valueVar, ) + .transformMapEntry(indexVar, valueVar, , ) + .transformMapEntry(keyVar, valueVar, ) + .transformMapEntry(keyVar, valueVar, , ) Examples: @@ -945,3 +967,73 @@ Examples: {'greeting': 'aloha', 'farewell': 'aloha'} .transformMapEntry(keyVar, valueVar, {valueVar: keyVar}) // error, duplicate key + +## Regex + +Regex introduces functions for regular expressions in CEL. + +Note: Please ensure that the cel.OptionalTypes() is enabled when using regex +extensions. All functions use the 'regex' namespace. If you are currently +using a variable named 'regex', the functions will likely work as intended. +However, there is some chance for collision. + +### Replace + +The `regex.replace` function replaces all non-overlapping substring of a regex +pattern in the target string with a replacement string. Optionally, you can +limit the number of replacements by providing a count argument. When the count +is a negative number, the function acts as replace all. Only numeric (\N) +capture group references are supported in the replacement string, with +validation for correctness. Backslashed-escaped digits (\1 to \9) within the +replacement argument can be used to insert text matching the corresponding +parenthesized group in the regexp pattern. An error will be thrown for invalid +regex or replace string. + + + regex.replace(target: string, pattern: string, replacement: string) -> string + regex.replace(target: string, pattern: string, replacement: string, count: int) -> string + + +Examples: + + regex.replace('hello world hello', 'hello', 'hi') == 'hi world hi' + regex.replace('banana', 'a', 'x', 0) == 'banana' + regex.replace('banana', 'a', 'x', 1) == 'bxnana' + regex.replace('banana', 'a', 'x', 2) == 'bxnxna' + regex.replace('banana', 'a', 'x', -12) == 'bxnxnx' + regex.replace('foo bar', '(fo)o (ba)r', '\\2 \\1') == 'ba fo' + + regex.replace('test', '(.)', '$2') \\ Runtime Error invalid replace string + regex.replace('foo bar', '(', '$2 $1') \\ Runtime Error invalid regex string + regex.replace('id=123', 'id=(?P\\\\d+)', 'value: \\values') \\ Runtime Error invalid replace string + +### Extract + +The `regex.extract` function returns the first match of a regex pattern as an +`optional` string. If no match is found, it returns an optional none value. +An error will be thrown for invalid regex or for multiple capture groups. + + regex.extract(target: string, pattern: string) -> optional + +Examples: + + regex.extract('hello world', 'hello(.*)') == optional.of(' world') + regex.extract('item-A, item-B', 'item-(\\w+)') == optional.of('A') + regex.extract('HELLO', 'hello') == optional.none() + + regex.extract('testuser@testdomain', '(.*)@([^.]*)')) \\ Runtime Error multiple extract group + +### Extract All + +The `regex.extractAll` function returns a `list` of all matches of a regex +pattern in a target string. If no matches are found, it returns an empty list. +An error will be thrown for invalid regex or for multiple capture groups. + + regex.extractAll(target: string, pattern: string) -> list + +Examples: + + regex.extractAll('id:123, id:456', 'id:\\d+') == ['id:123', 'id:456'] + regex.extractAll('id:123, id:456', 'assa') == [] + + regex.extractAll('testuser@testdomain', '(.*)@([^.]*)') \\ Runtime Error multiple capture group diff --git a/vendor/github.com/google/cel-go/ext/bindings.go b/vendor/github.com/google/cel-go/ext/bindings.go index 63942b85c..89766d60a 100644 --- a/vendor/github.com/google/cel-go/ext/bindings.go +++ b/vendor/github.com/google/cel-go/ext/bindings.go @@ -108,7 +108,7 @@ func (lib *celBindings) CompileOptions() []cel.EnvOption { func (lib *celBindings) ProgramOptions() []cel.ProgramOption { if lib.version >= 1 { - celBlockPlan := func(i interpreter.Interpretable) (interpreter.Interpretable, error) { + celBlockPlan := func(i interpreter.InterpretableV2) (interpreter.InterpretableV2, error) { call, ok := i.(interpreter.InterpretableCall) if !ok { return i, nil @@ -140,7 +140,7 @@ func (lib *celBindings) ProgramOptions() []cel.ProgramOption { return i, nil } } - return []cel.ProgramOption{cel.CustomDecorator(celBlockPlan)} + return []cel.ProgramOption{cel.CustomDecoratorV2(celBlockPlan)} } return []cel.ProgramOption{} } @@ -190,7 +190,7 @@ func celBind(mef cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Ex ), nil } -func newDynamicBlock(slotExprs []interpreter.Interpretable, expr interpreter.Interpretable) interpreter.Interpretable { +func newDynamicBlock(slotExprs []interpreter.InterpretableV2, expr interpreter.InterpretableV2) interpreter.InterpretableV2 { bs := &dynamicBlock{ slotExprs: slotExprs, expr: expr, @@ -213,8 +213,8 @@ func newDynamicBlock(slotExprs []interpreter.Interpretable, expr interpreter.Int } type dynamicBlock struct { - slotExprs []interpreter.Interpretable - expr interpreter.Interpretable + slotExprs []interpreter.InterpretableV2 + expr interpreter.InterpretableV2 slotActivationPool *sync.Pool } @@ -223,12 +223,23 @@ func (b *dynamicBlock) ID() int64 { return b.expr.ID() } -// Eval implements the Interpretable interface method. -func (b *dynamicBlock) Eval(activation cel.Activation) ref.Val { +// Exec implements the Interpretable interface method and pushes a new frame onto the +// execution frame for the duration of the block execution. +func (b *dynamicBlock) Exec(frame *interpreter.ExecutionFrame) ref.Val { sa := b.slotActivationPool.Get().(*dynamicSlotActivation) - sa.Activation = activation + sa.frame = frame.Push(sa) + // Ensure the 'unwrapped' Activation points to the original one from the frame, + // and not the hierarchical activation which composes the original and the slot + // activation. + sa.Activation = frame.Activation + defer sa.frame.Pop() defer b.clearSlots(sa) - return b.expr.Eval(sa) + return b.expr.Exec(sa.frame) +} + +// Eval implements the Interpretable interface method. +func (b *dynamicBlock) Eval(activation cel.Activation) ref.Val { + return b.Exec(interpreter.AsFrame(activation)) } func (b *dynamicBlock) clearSlots(sa *dynamicSlotActivation) { @@ -243,11 +254,17 @@ type slotVal struct { type dynamicSlotActivation struct { cel.Activation - slotExprs []interpreter.Interpretable + frame *interpreter.ExecutionFrame + slotExprs []interpreter.InterpretableV2 slotCount int slotVals []*slotVal } +// Unwrap returns the underlying activation. +func (sa *dynamicSlotActivation) Unwrap() cel.Activation { + return sa.Activation +} + // ResolveName implements the Activation interface method but handles variables prefixed with `@index` // as special variables which exist within the slot-based memory of the cel.@block() where each slot // refers to an expression which must be computed only once. @@ -262,7 +279,7 @@ func (sa *dynamicSlotActivation) ResolveName(name string) (any, bool) { return *v.value, true } v.visited = true - val := sa.slotExprs[idx].Eval(sa) + val := sa.slotExprs[idx].Exec(sa.frame) v.value = &val return val, true } @@ -271,13 +288,14 @@ func (sa *dynamicSlotActivation) ResolveName(name string) (any, bool) { func (sa *dynamicSlotActivation) reset() { sa.Activation = nil + sa.frame = nil for _, sv := range sa.slotVals { sv.visited = false sv.value = nil } } -func newConstantBlock(slots traits.Lister, expr interpreter.Interpretable) interpreter.Interpretable { +func newConstantBlock(slots traits.Lister, expr interpreter.InterpretableV2) interpreter.InterpretableV2 { count := slots.Size().(types.Int) return &constantBlock{slots: slots, slotCount: int(count), expr: expr} } @@ -285,7 +303,7 @@ func newConstantBlock(slots traits.Lister, expr interpreter.Interpretable) inter type constantBlock struct { slots traits.Lister slotCount int - expr interpreter.Interpretable + expr interpreter.InterpretableV2 } // ID implements the interpreter.Interpretable interface method. @@ -293,19 +311,33 @@ func (b *constantBlock) ID() int64 { return b.expr.ID() } +// Exec implements the Interpretable interface method and pushes a new frame onto the +// stack for the duration of the block execution. +func (b *constantBlock) Exec(frame *interpreter.ExecutionFrame) ref.Val { + sa := constantSlotActivation{Activation: frame.Activation, slots: b.slots, slotCount: b.slotCount} + sa.frame = frame.Push(sa) + defer sa.frame.Pop() + return b.expr.Exec(sa.frame) +} + // Eval implements the interpreter.Interpretable interface method, and will proxy @index prefixed variable // lookups into a set of constant slots determined from the plan step. func (b *constantBlock) Eval(activation cel.Activation) ref.Val { - vars := constantSlotActivation{Activation: activation, slots: b.slots, slotCount: b.slotCount} - return b.expr.Eval(vars) + return b.Exec(interpreter.AsFrame(activation)) } type constantSlotActivation struct { cel.Activation + frame *interpreter.ExecutionFrame slots traits.Lister slotCount int } +// Unwrap returns the underlying activation. +func (sa *constantSlotActivation) Unwrap() cel.Activation { + return sa.Activation +} + // ResolveName implements Activation interface method and proxies @index prefixed lookups into the slot // activation associated with the block scope. func (sa constantSlotActivation) ResolveName(name string) (any, bool) { diff --git a/vendor/github.com/google/cel-go/ext/comprehensions.go b/vendor/github.com/google/cel-go/ext/comprehensions.go index f08d8f9da..adb22912b 100644 --- a/vendor/github.com/google/cel-go/ext/comprehensions.go +++ b/vendor/github.com/google/cel-go/ext/comprehensions.go @@ -146,10 +146,10 @@ const ( // Elements in the map may optionally be filtered according to a predicate expression, where // elements that satisfy the predicate are transformed. // -// .transformMap(indexVar, valueVar, ) -// .transformMap(indexVar, valueVar, , ) -// .transformMap(keyVar, valueVar, ) -// .transformMap(keyVar, valueVar, , ) +// .transformMapEntry(indexVar, valueVar, ) +// .transformMapEntry(indexVar, valueVar, , ) +// .transformMapEntry(keyVar, valueVar, ) +// .transformMapEntry(keyVar, valueVar, , ) // // Examples: // diff --git a/vendor/github.com/google/cel-go/ext/costs.go b/vendor/github.com/google/cel-go/ext/costs.go new file mode 100644 index 000000000..d2cf7c757 --- /dev/null +++ b/vendor/github.com/google/cel-go/ext/costs.go @@ -0,0 +1,122 @@ +// Copyright 2026 Google LLC +// +// 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 ext + +import ( + "math" + + "github.com/google/cel-go/checker" + "github.com/google/cel-go/common" + "github.com/google/cel-go/common/ast" + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" + "github.com/google/cel-go/common/types/traits" +) + +var ( + callCostEstimate = checker.FixedCostEstimate(1) + callCost = uint64(1) + listAllocCost = checker.FixedCostEstimate(common.ListCreateBaseCost) + stringCostFactor = common.StringTraversalCostFactor +) + +func estimateStringScan(sz checker.SizeEstimate) (checker.CostEstimate, *checker.SizeEstimate) { + return estimateTraversal(sz, stringCostFactor, nil) +} + +func estimateListAlloc(sz checker.SizeEstimate, costFactor float64) (checker.CostEstimate, *checker.SizeEstimate) { + return estimateTraversal(sz, costFactor, &listAllocCost) +} + +// estimateTraversal computes cost as a function of the size of the target object and whether the call allocates memory. +func estimateTraversal(nodeSize checker.SizeEstimate, costFactor float64, allocationCost *checker.CostEstimate) (checker.CostEstimate, *checker.SizeEstimate) { + cost := nodeSize.MultiplyByCostFactor(costFactor) + if allocationCost != nil { + cost = cost.Add(*allocationCost) + } + return cost, &nodeSize +} + +func estimateSize(estimator checker.CostEstimator, node checker.AstNode) checker.SizeEstimate { + if l := node.ComputedSize(); l != nil { + return *l + } + if l := estimator.EstimateSize(node); l != nil { + return *l + } + return checker.SizeEstimate{Min: 0, Max: math.MaxUint64} +} + +func actualSize(value ref.Val) uint64 { + if sz, ok := value.(traits.Sizer); ok { + return uint64(sz.Size().(types.Int)) + } + return 1 +} + +func nodeAsUintValue(node checker.AstNode, defaultVal uint64) uint64 { + if node.Expr().Kind() != ast.LiteralKind { + return defaultVal + } + lit := node.Expr().AsLiteral() + if lit.Type() != types.IntType { + return defaultVal + } + val := lit.(types.Int) + if val < types.IntZero { + return 0 + } + return uint64(lit.(types.Int)) +} + +func callEstimate(cost checker.CostEstimate, sz *checker.SizeEstimate) *checker.CallEstimate { + return &checker.CallEstimate{CostEstimate: cost, ResultSize: sz} +} + +func rangedSizeEstimate(min, max uint64) checker.SizeEstimate { + return checker.SizeEstimate{Min: min, Max: max} +} + +func fixedSizeEstimate(val uint64) checker.SizeEstimate { + return checker.FixedSizeEstimate(val) +} + +func atLeastOne(size checker.SizeEstimate) checker.SizeEstimate { + if size.Min == 0 { + size.Min = 1 + } + if size.Max == 0 { + size.Max = 1 + } + return size +} + +func safeAdd(x, y uint64, rest ...uint64) uint64 { + if y > 0 && x > math.MaxUint64-y { + return math.MaxUint64 + } + next := x + y + if len(rest) == 0 { + return next + } + return safeAdd(next, rest[0], rest[1:]...) +} + +func safeMul(x, y uint64) uint64 { + if y != 0 && x > math.MaxUint64/y { + return math.MaxUint64 + } + return x * y +} diff --git a/vendor/github.com/google/cel-go/ext/encoders.go b/vendor/github.com/google/cel-go/ext/encoders.go index 731c3d095..97fc932a5 100644 --- a/vendor/github.com/google/cel-go/ext/encoders.go +++ b/vendor/github.com/google/cel-go/ext/encoders.go @@ -16,11 +16,17 @@ package ext import ( "encoding/base64" + "encoding/json" + "fmt" "math" "github.com/google/cel-go/cel" + "github.com/google/cel-go/checker" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" + "github.com/google/cel-go/interpreter" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/types/known/structpb" ) // Encoders returns a cel.EnvOption to configure extended functions for string, byte, and object @@ -48,6 +54,18 @@ import ( // Examples: // // base64.encode(b'hello') // return b'aGVsbG8=' +// +// # JSON.Encode +// +// Introduced at version: 1 +// +// Encodes a CEL value to a JSON string. +// +// json.encode() -> +// +// Examples: +// +// json.encode({'hello': 'world'}) // return '{"hello":"world"}' func Encoders(options ...EncodersOption) cel.EnvOption { l := &encoderLib{version: math.MaxUint32} for _, o := range options { @@ -75,8 +93,8 @@ func (*encoderLib) LibraryName() string { return "cel.lib.ext.encoders" } -func (*encoderLib) CompileOptions() []cel.EnvOption { - return []cel.EnvOption{ +func (lib *encoderLib) CompileOptions() []cel.EnvOption { + opts := []cel.EnvOption{ cel.Function("base64.decode", cel.Overload("base64_decode_string", []*cel.Type{cel.StringType}, cel.BytesType, cel.UnaryBinding(func(str ref.Val) ref.Val { @@ -90,10 +108,35 @@ func (*encoderLib) CompileOptions() []cel.EnvOption { return stringOrError(base64EncodeBytes([]byte(b))) }))), } + if lib.version >= 1 { + estimators := []checker.CostOption{ + checker.OverloadCostEstimate("base64_decode_string", estimateDecode), + checker.OverloadCostEstimate("base64_encode_bytes", estimateEncode), + checker.OverloadCostEstimate("json_encode_dyn", estimateJSONEncode), + } + opts = append(opts, cel.CostEstimatorOptions(estimators...)) + opts = append(opts, + cel.Function("json.encode", + cel.Overload("json_encode_dyn", []*cel.Type{cel.DynType}, cel.StringType, + cel.UnaryBinding(func(val ref.Val) ref.Val { + return stringOrError(jsonEncodeValue(val)) + }))), + ) + } + return opts } -func (*encoderLib) ProgramOptions() []cel.ProgramOption { - return []cel.ProgramOption{} +func (lib *encoderLib) ProgramOptions() []cel.ProgramOption { + var opts []cel.ProgramOption + if lib.version >= 1 { + trackers := []interpreter.CostTrackerOption{ + interpreter.OverloadCostTracker("base64_decode_string", trackDecode), + interpreter.OverloadCostTracker("base64_encode_bytes", trackEncode), + interpreter.OverloadCostTracker("json_encode_dyn", trackJSONEncode), + } + opts = append(opts, cel.CostTrackerOptions(trackers...)) + } + return opts } func base64DecodeString(str string) ([]byte, error) { @@ -110,3 +153,93 @@ func base64DecodeString(str string) ([]byte, error) { func base64EncodeBytes(bytes []byte) (string, error) { return base64.StdEncoding.EncodeToString(bytes), nil } + +func estimateEncode(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if len(args) != 1 { + return nil + } + sz := estimateSize(estimator, args[0]) + cost := sz.MultiplyByCostFactor(stringCostFactor).Add(callCostEstimate) + resSize := estimateEncodeSize(sz) + return &checker.CallEstimate{CostEstimate: cost, ResultSize: &resSize} +} + +func estimateJSONEncode(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if len(args) != 1 { + return nil + } + size := estimateJSONEncodeSize() + return &checker.CallEstimate{CostEstimate: checker.UnknownCostEstimate(), ResultSize: &size} +} + +func estimateDecode(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if len(args) != 1 { + return nil + } + sz := estimateSize(estimator, args[0]) + cost := sz.MultiplyByCostFactor(stringCostFactor).Add(callCostEstimate) + resSize := estimateDecodeSize(sz) + return &checker.CallEstimate{CostEstimate: cost, ResultSize: &resSize} +} + +func trackEncode(args []ref.Val, _ ref.Val) *uint64 { + sz := actualSize(args[0]) + cost := uint64(math.Ceil(float64(sz)*stringCostFactor)) + callCost + return &cost +} + +func trackJSONEncode(args []ref.Val, _ ref.Val) *uint64 { + maxCost := uint64(math.MaxUint64) + return &maxCost +} + +func trackDecode(args []ref.Val, _ ref.Val) *uint64 { + sz := actualSize(args[0]) + cost := uint64(math.Ceil(float64(sz)*stringCostFactor)) + callCost + return &cost +} + +func estimateEncodeSize(sz checker.SizeEstimate) checker.SizeEstimate { + minVal := (sz.Min*4 + 2) / 3 + maxVal := (sz.Max*4 + 2) / 3 + if sz.Max > math.MaxUint64/4 { + maxVal = math.MaxUint64 + } + return checker.SizeEstimate{Min: minVal, Max: maxVal} +} + +func estimateJSONEncodeSize() checker.SizeEstimate { + // TODO: provide a more sophisticated size estimate based on the CEL value's type. + return checker.UnknownSizeEstimate() +} + +func estimateDecodeSize(sz checker.SizeEstimate) checker.SizeEstimate { + minVal := sz.Min * 3 / 4 + maxVal := sz.Max * 3 / 4 + return checker.SizeEstimate{Min: minVal, Max: maxVal} +} + +func jsonEncodeValue(val ref.Val) (string, error) { + native, err := val.ConvertToNative(types.JSONValueType) + if err != nil { + return "", err + } + jsonValue, ok := native.(*structpb.Value) + if !ok { + return "", fmt.Errorf("cannot convert %T to JSON value", native) + } + jsonBytes, err := protojson.Marshal(jsonValue) + if err != nil { + return "", err + } + var obj interface{} + if err := json.Unmarshal(jsonBytes, &obj); err != nil { + return "", fmt.Errorf("unmarshaling protojson: %w", err) + } + // Re-marshal with standard json.Marshal for deterministic compact output + jsonBytes, err = json.Marshal(obj) + if err != nil { + return "", fmt.Errorf("re-marshaling value: %w", err) + } + return string(jsonBytes), nil +} diff --git a/vendor/github.com/google/cel-go/ext/extension_option_factory.go b/vendor/github.com/google/cel-go/ext/extension_option_factory.go index cebf0d760..e68cf5bc7 100644 --- a/vendor/github.com/google/cel-go/ext/extension_option_factory.go +++ b/vendor/github.com/google/cel-go/ext/extension_option_factory.go @@ -27,7 +27,12 @@ func ExtensionOptionFactory(configElement any) (cel.EnvOption, bool) { if !isExtension { return nil, false } - fac, found := extFactories[ext.Name] + name := ext.Name + alias, found := extAliases[name] + if found { + name = alias + } + fac, found := extFactories[name] if !found { return nil, false } @@ -45,31 +50,43 @@ func ExtensionOptionFactory(configElement any) (cel.EnvOption, bool) { type extensionFactory func(uint32) cel.EnvOption var extFactories = map[string]extensionFactory{ - "bindings": func(version uint32) cel.EnvOption { + "cel.lib.ext.cel.bindings": func(version uint32) cel.EnvOption { return Bindings(BindingsVersion(version)) }, - "encoders": func(version uint32) cel.EnvOption { + "cel.lib.ext.encoders": func(version uint32) cel.EnvOption { return Encoders(EncodersVersion(version)) }, - "lists": func(version uint32) cel.EnvOption { + "cel.lib.ext.lists": func(version uint32) cel.EnvOption { return Lists(ListsVersion(version)) }, - "math": func(version uint32) cel.EnvOption { + "cel.lib.ext.math": func(version uint32) cel.EnvOption { return Math(MathVersion(version)) }, - "protos": func(version uint32) cel.EnvOption { + "cel.lib.ext.protos": func(version uint32) cel.EnvOption { return Protos(ProtosVersion(version)) }, - "sets": func(version uint32) cel.EnvOption { + "cel.lib.ext.sets": func(version uint32) cel.EnvOption { return Sets(SetsVersion(version)) }, - "strings": func(version uint32) cel.EnvOption { + "cel.lib.ext.strings": func(version uint32) cel.EnvOption { return Strings(StringsVersion(version)) }, - "two-var-comprehensions": func(version uint32) cel.EnvOption { + "cel.lib.ext.comprev2": func(version uint32) cel.EnvOption { return TwoVarComprehensions(TwoVarComprehensionsVersion(version)) }, - "regex": func(version uint32) cel.EnvOption { + "cel.lib.ext.regex": func(version uint32) cel.EnvOption { return Regex(RegexVersion(version)) }, } + +var extAliases = map[string]string{ + "bindings": "cel.lib.ext.cel.bindings", + "encoders": "cel.lib.ext.encoders", + "lists": "cel.lib.ext.lists", + "math": "cel.lib.ext.math", + "protos": "cel.lib.ext.protos", + "sets": "cel.lib.ext.sets", + "strings": "cel.lib.ext.strings", + "two-var-comprehensions": "cel.lib.ext.comprev2", + "regex": "cel.lib.ext.regex", +} diff --git a/vendor/github.com/google/cel-go/ext/formatting.go b/vendor/github.com/google/cel-go/ext/formatting.go index 111184b73..35fb17048 100644 --- a/vendor/github.com/google/cel-go/ext/formatting.go +++ b/vendor/github.com/google/cel-go/ext/formatting.go @@ -410,7 +410,9 @@ func (c *stringFormatter) Octal(arg ref.Val, locale string) (string, error) { // stringFormatValidator implements the cel.ASTValidator interface allowing for static validation // of string.format calls. -type stringFormatValidator struct{} +type stringFormatValidator struct { + maxPrecision int +} // Name returns the name of the validator. func (stringFormatValidator) Name() string { @@ -427,7 +429,7 @@ func (stringFormatValidator) Configure(config cel.MutableValidatorConfig) error // Validate parses all literal format strings and type checks the format clause against the argument // at the corresponding ordinal within the list literal argument to the function, if one is specified. -func (stringFormatValidator) Validate(env *cel.Env, _ cel.ValidatorConfig, a *ast.AST, iss *cel.Issues) { +func (v stringFormatValidator) Validate(env *cel.Env, _ cel.ValidatorConfig, a *ast.AST, iss *cel.Issues) { root := ast.NavigateAST(a) formatCallExprs := ast.MatchDescendants(root, matchConstantFormatStringWithListLiteralArgs(a)) for _, e := range formatCallExprs { @@ -439,7 +441,7 @@ func (stringFormatValidator) Validate(env *cel.Env, _ cel.ValidatorConfig, a *as ast: a, } // use a placeholder locale, since locale doesn't affect syntax - _, err := parseFormatString(formatStr, formatCheck, formatCheck, "en_US") + _, err := parseFormatString(formatStr, formatCheck, formatCheck, "en_US", v.maxPrecision) if err != nil { iss.ReportErrorAtID(getErrorExprID(e.ID(), err), "%v", err) continue @@ -778,7 +780,7 @@ type formatListArgs interface { // parseFormatString formats a string according to the string.format syntax, taking the clause implementations // from the provided FormatCallback and the args from the given FormatList. -func parseFormatString(formatStr string, callback formatStringInterpolator, list formatListArgs, locale string) (string, error) { +func parseFormatString(formatStr string, callback formatStringInterpolator, list formatListArgs, locale string, maxPrecision int) (string, error) { i := 0 argIndex := 0 var builtStr strings.Builder @@ -802,7 +804,7 @@ func parseFormatString(formatStr string, callback formatStringInterpolator, list if int64(argIndex) >= list.Size() { return "", fmt.Errorf("index %d out of range", argIndex) } - numRead, val, refErr := parseAndFormatClause(formatStr[i:], argAny, callback, list, locale) + numRead, val, refErr := parseAndFormatClause(formatStr[i:], argAny, callback, list, locale, maxPrecision) if refErr != nil { return "", refErr } @@ -826,9 +828,9 @@ func parseFormatString(formatStr string, callback formatStringInterpolator, list // parseAndFormatClause parses the format clause at the start of the given string with val, and returns // how many characters were consumed and the substituted string form of val, or an error if one occurred. -func parseAndFormatClause(formatStr string, val ref.Val, callback formatStringInterpolator, list formatListArgs, locale string) (int, string, error) { +func parseAndFormatClause(formatStr string, val ref.Val, callback formatStringInterpolator, list formatListArgs, locale string, maxPrecision int) (int, string, error) { i := 1 - read, formatter, err := parseFormattingClause(formatStr[i:], callback) + read, formatter, err := parseFormattingClause(formatStr[i:], callback, maxPrecision) i += read if err != nil { return -1, "", newParseFormatError("could not parse formatting clause", err) @@ -841,9 +843,9 @@ func parseAndFormatClause(formatStr string, val ref.Val, callback formatStringIn return i, valStr, nil } -func parseFormattingClause(formatStr string, callback formatStringInterpolator) (int, clauseImpl, error) { +func parseFormattingClause(formatStr string, callback formatStringInterpolator, maxPrecision int) (int, clauseImpl, error) { i := 0 - read, precision, err := parsePrecision(formatStr[i:]) + read, precision, err := parsePrecision(formatStr[i:], maxPrecision) i += read if err != nil { return -1, nil, fmt.Errorf("error while parsing precision: %w", err) @@ -870,7 +872,7 @@ func parseFormattingClause(formatStr string, callback formatStringInterpolator) } } -func parsePrecision(formatStr string) (int, *int, error) { +func parsePrecision(formatStr string, maxPrecision int) (int, *int, error) { i := 0 if formatStr[i] != '.' { return i, nil, nil @@ -891,6 +893,9 @@ func parsePrecision(formatStr string) (int, *int, error) { if err != nil { return -1, nil, fmt.Errorf("error while converting precision to integer: %w", err) } + if maxPrecision > 0 && precision > maxPrecision { + return -1, nil, fmt.Errorf("precision %d exceeds maximum allowed precision %d", precision, maxPrecision) + } return i, &precision, nil } diff --git a/vendor/github.com/google/cel-go/ext/formatting_v2.go b/vendor/github.com/google/cel-go/ext/formatting_v2.go index ca8efbc4e..f923cc7e1 100644 --- a/vendor/github.com/google/cel-go/ext/formatting_v2.go +++ b/vendor/github.com/google/cel-go/ext/formatting_v2.go @@ -245,13 +245,13 @@ func (c *stringFormatterV2) Fixed(precision int) func(ref.Val) (string, error) { if !ok { return "", fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.IntType) } - return fmt.Sprintf(fmtStr, argInt), nil + return fmt.Sprintf(fmtStr, float64(argInt)), nil case types.UintType: argUint, ok := arg.Value().(uint64) if !ok { return "", fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.UintType) } - return fmt.Sprintf(fmtStr, argUint), nil + return fmt.Sprintf(fmtStr, float64(argUint)), nil case types.DoubleType: argDbl, ok := arg.Value().(float64) if !ok { @@ -283,13 +283,13 @@ func (c *stringFormatterV2) Scientific(precision int) func(ref.Val) (string, err if !ok { return "", fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.IntType) } - return fmt.Sprintf(fmtStr, argInt), nil + return fmt.Sprintf(fmtStr, float64(argInt)), nil case types.UintType: argUint, ok := arg.Value().(uint64) if !ok { return "", fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.UintType) } - return fmt.Sprintf(fmtStr, argUint), nil + return fmt.Sprintf(fmtStr, float64(argUint)), nil case types.DoubleType: argDbl, ok := arg.Value().(float64) if !ok { @@ -402,7 +402,9 @@ func (c *stringFormatterV2) Octal(arg ref.Val) (string, error) { // stringFormatValidatorV2 implements the cel.ASTValidator interface allowing for static validation // of string.format calls. -type stringFormatValidatorV2 struct{} +type stringFormatValidatorV2 struct { + maxPrecision int +} // Name returns the name of the validator. func (stringFormatValidatorV2) Name() string { @@ -419,7 +421,7 @@ func (stringFormatValidatorV2) Configure(config cel.MutableValidatorConfig) erro // Validate parses all literal format strings and type checks the format clause against the argument // at the corresponding ordinal within the list literal argument to the function, if one is specified. -func (stringFormatValidatorV2) Validate(env *cel.Env, _ cel.ValidatorConfig, a *ast.AST, iss *cel.Issues) { +func (v stringFormatValidatorV2) Validate(env *cel.Env, _ cel.ValidatorConfig, a *ast.AST, iss *cel.Issues) { root := ast.NavigateAST(a) formatCallExprs := ast.MatchDescendants(root, matchConstantFormatStringWithListLiteralArgs(a)) for _, e := range formatCallExprs { @@ -431,7 +433,7 @@ func (stringFormatValidatorV2) Validate(env *cel.Env, _ cel.ValidatorConfig, a * ast: a, } // use a placeholder locale, since locale doesn't affect syntax - _, err := parseFormatStringV2(formatStr, formatCheck, formatCheck) + _, err := parseFormatStringV2(formatStr, formatCheck, formatCheck, v.maxPrecision) if err != nil { iss.ReportErrorAtID(getErrorExprID(e.ID(), err), "%v", err) continue @@ -668,7 +670,7 @@ type formatStringInterpolatorV2 interface { // parseFormatString formats a string according to the string.format syntax, taking the clause implementations // from the provided FormatCallback and the args from the given FormatList. -func parseFormatStringV2(formatStr string, callback formatStringInterpolatorV2, list formatListArgs) (string, error) { +func parseFormatStringV2(formatStr string, callback formatStringInterpolatorV2, list formatListArgs, maxPrecision int) (string, error) { i := 0 argIndex := 0 var builtStr strings.Builder @@ -692,7 +694,7 @@ func parseFormatStringV2(formatStr string, callback formatStringInterpolatorV2, if int64(argIndex) >= list.Size() { return "", fmt.Errorf("index %d out of range", argIndex) } - numRead, val, refErr := parseAndFormatClauseV2(formatStr[i:], argAny, callback, list) + numRead, val, refErr := parseAndFormatClauseV2(formatStr[i:], argAny, callback, list, maxPrecision) if refErr != nil { return "", refErr } @@ -716,9 +718,9 @@ func parseFormatStringV2(formatStr string, callback formatStringInterpolatorV2, // parseAndFormatClause parses the format clause at the start of the given string with val, and returns // how many characters were consumed and the substituted string form of val, or an error if one occurred. -func parseAndFormatClauseV2(formatStr string, val ref.Val, callback formatStringInterpolatorV2, list formatListArgs) (int, string, error) { +func parseAndFormatClauseV2(formatStr string, val ref.Val, callback formatStringInterpolatorV2, list formatListArgs, maxPrecision int) (int, string, error) { i := 1 - read, formatter, err := parseFormattingClauseV2(formatStr[i:], callback) + read, formatter, err := parseFormattingClauseV2(formatStr[i:], callback, maxPrecision) i += read if err != nil { return -1, "", newParseFormatError("could not parse formatting clause", err) @@ -731,9 +733,9 @@ func parseAndFormatClauseV2(formatStr string, val ref.Val, callback formatString return i, valStr, nil } -func parseFormattingClauseV2(formatStr string, callback formatStringInterpolatorV2) (int, clauseImplV2, error) { +func parseFormattingClauseV2(formatStr string, callback formatStringInterpolatorV2, maxPrecision int) (int, clauseImplV2, error) { i := 0 - read, precision, err := parsePrecisionV2(formatStr[i:]) + read, precision, err := parsePrecisionV2(formatStr[i:], maxPrecision) i += read if err != nil { return -1, nil, fmt.Errorf("error while parsing precision: %w", err) @@ -760,7 +762,7 @@ func parseFormattingClauseV2(formatStr string, callback formatStringInterpolator } } -func parsePrecisionV2(formatStr string) (int, int, error) { +func parsePrecisionV2(formatStr string, maxPrecision int) (int, int, error) { i := 0 if formatStr[i] != '.' { return i, defaultPrecision, nil @@ -784,5 +786,8 @@ func parsePrecisionV2(formatStr string) (int, int, error) { if precision < 0 { return -1, -1, fmt.Errorf("negative precision: %d", precision) } + if maxPrecision > 0 && precision > maxPrecision { + return -1, -1, fmt.Errorf("precision %d exceeds maximum allowed precision %d", precision, maxPrecision) + } return i, precision, nil } diff --git a/vendor/github.com/google/cel-go/ext/lists.go b/vendor/github.com/google/cel-go/ext/lists.go index b27ddf22f..2196d218c 100644 --- a/vendor/github.com/google/cel-go/ext/lists.go +++ b/vendor/github.com/google/cel-go/ext/lists.go @@ -153,15 +153,18 @@ var comparableTypes = []*cel.Type{ // ].sortBy(e, e.score).map(e, e.name) // == ["bar", "foo", "baz"] func Lists(options ...ListsOption) cel.EnvOption { - l := &listsLib{version: math.MaxUint32} + l := &listsLib{version: math.MaxUint32, maxRangeSize: defaultMaxRangeSize} for _, o := range options { l = o(l) } return cel.Lib(l) } +const defaultMaxRangeSize = 1_000_000 + type listsLib struct { - version uint32 + version uint32 + maxRangeSize int64 } // LibraryName implements the SingletonLibrary interface method. @@ -188,6 +191,16 @@ func ListsVersion(version uint32) ListsOption { } } +// ListsMaxRangeSize sets the maximum number of elements lists.range() will +// allocate. If not set, the default is 1,000,000. Setting this to zero +// disables the limit (not recommended). +func ListsMaxRangeSize(size int64) ListsOption { + return func(lib *listsLib) *listsLib { + lib.maxRangeSize = size + return lib + } +} + // CompileOptions implements the Library interface method. func (lib listsLib) CompileOptions() []cel.EnvOption { listType := cel.ListType(cel.TypeParamType("T")) @@ -309,11 +322,12 @@ func (lib listsLib) CompileOptions() []cel.EnvOption { )..., )) + maxRange := lib.maxRangeSize opts = append(opts, cel.Function("lists.range", cel.Overload("lists_range", []*cel.Type{cel.IntType}, cel.ListType(cel.IntType), cel.UnaryBinding(func(n ref.Val) ref.Val { - result, err := genRange(n.(types.Int)) + result, err := genRange(n.(types.Int), maxRange) if err != nil { return types.WrapErr(err) } @@ -349,23 +363,45 @@ func (lib listsLib) CompileOptions() []cel.EnvOption { if lib.version >= 3 { estimators := []checker.CostOption{ checker.OverloadCostEstimate("list_slice", estimateListSlice), - checker.OverloadCostEstimate("list_flatten", estimateListFlatten), - checker.OverloadCostEstimate("list_flatten_int", estimateListFlatten), checker.OverloadCostEstimate("lists_range", estimateListsRange), checker.OverloadCostEstimate("list_reverse", estimateListReverse), - checker.OverloadCostEstimate("list_distinct", estimateListDistinct), } - for _, t := range comparableTypes { + if lib.version == 3 { estimators = append(estimators, - checker.OverloadCostEstimate( - fmt.Sprintf("list_%s_sort", t.TypeName()), - estimateListSort(t), - ), - checker.OverloadCostEstimate( - fmt.Sprintf("list_%s_sortByAssociatedKeys", t.TypeName()), - estimateListSortBy(t), - ), + checker.OverloadCostEstimate("list_flatten", estimateListFlattenLegacy), + checker.OverloadCostEstimate("list_flatten_int", estimateListFlattenLegacy), + checker.OverloadCostEstimate("list_distinct", estimateListDistinctLegacy), ) + for _, t := range comparableTypes { + estimators = append(estimators, + checker.OverloadCostEstimate( + fmt.Sprintf("list_%s_sort", t.TypeName()), + estimateListSortLegacy(t), + ), + checker.OverloadCostEstimate( + fmt.Sprintf("list_%s_sortByAssociatedKeys", t.TypeName()), + estimateListSortByLegacy(t), + ), + ) + } + } else { + estimators = append(estimators, + checker.OverloadCostEstimate("list_flatten", estimateListFlatten), + checker.OverloadCostEstimate("list_flatten_int", estimateListFlatten), + checker.OverloadCostEstimate("list_distinct", estimateListDistinct), + ) + for _, t := range comparableTypes { + estimators = append(estimators, + checker.OverloadCostEstimate( + fmt.Sprintf("list_%s_sort", t.TypeName()), + estimateListSort(t), + ), + checker.OverloadCostEstimate( + fmt.Sprintf("list_%s_sortByAssociatedKeys", t.TypeName()), + estimateListSortBy(t), + ), + ) + } } opts = append(opts, cel.CostEstimatorOptions(estimators...)) } @@ -377,15 +413,23 @@ func (lib listsLib) CompileOptions() []cel.EnvOption { func (lib *listsLib) ProgramOptions() []cel.ProgramOption { var opts []cel.ProgramOption if lib.version >= 3 { - // TODO: Add cost trackers for list operations trackers := []interpreter.CostTrackerOption{ interpreter.OverloadCostTracker("list_slice", trackListOutputSize), - interpreter.OverloadCostTracker("list_flatten", trackListFlatten), - interpreter.OverloadCostTracker("list_flatten_int", trackListFlatten), interpreter.OverloadCostTracker("lists_range", trackListOutputSize), interpreter.OverloadCostTracker("list_reverse", trackListOutputSize), interpreter.OverloadCostTracker("list_distinct", trackListDistinct), } + if lib.version == 3 { + trackers = append(trackers, + interpreter.OverloadCostTracker("list_flatten", trackListFlattenLegacy), + interpreter.OverloadCostTracker("list_flatten_int", trackListFlattenLegacy), + ) + } else { + trackers = append(trackers, + interpreter.OverloadCostTracker("list_flatten", trackListFlatten), + interpreter.OverloadCostTracker("list_flatten_int", trackListFlatten), + ) + } for _, t := range comparableTypes { trackers = append(trackers, interpreter.OverloadCostTracker( @@ -403,8 +447,14 @@ func (lib *listsLib) ProgramOptions() []cel.ProgramOption { return opts } -func genRange(n types.Int) (ref.Val, error) { - var newList []ref.Val +func genRange(n types.Int, maxSize int64) (ref.Val, error) { + if n < 0 { + return nil, fmt.Errorf("lists.range: size must be non-negative, got %d", n) + } + if maxSize > 0 && int64(n) > maxSize { + return nil, fmt.Errorf("lists.range: size %d exceeds maximum allowed (%d)", n, maxSize) + } + newList := make([]ref.Val, 0, n) for i := types.Int(0); i < n; i++ { newList = append(newList, i) } @@ -616,8 +666,8 @@ func estimateListSlice(estimator checker.CostEstimator, target *checker.AstNode, return nil } sz := estimateSize(estimator, *target) - start := nodeAsIntValue(args[0], 0) - end := nodeAsIntValue(args[1], sz.Max) + start := nodeAsUintValue(args[0], 0) + end := nodeAsUintValue(args[1], sz.Max) return estimateAllocatingListCall(1, checker.FixedSizeEstimate(end-start)) } @@ -626,7 +676,7 @@ func estimateListsRange(estimator checker.CostEstimator, target *checker.AstNode if target != nil || len(args) != 1 { return nil } - return estimateAllocatingListCall(1, checker.FixedSizeEstimate(nodeAsIntValue(args[0], math.MaxUint))) + return estimateAllocatingListCall(1, checker.FixedSizeEstimate(nodeAsUintValue(args[0], math.MaxUint))) } // estimateListReverse computes an O(n) reverse operation with a cost factor of 1. @@ -637,18 +687,73 @@ func estimateListReverse(estimator checker.CostEstimator, target *checker.AstNod return estimateAllocatingListCall(1, estimateSize(estimator, *target)) } -// estimateListFlatten computes an O(n) flatten operation with a cost factor proportional to the flatten depth. +// estimateListFlatten computes an O(n) flatten operation with a cost factor proportional to the total number of flattened items. func estimateListFlatten(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { if target == nil || len(args) > 1 { return nil } depth := uint64(1) if len(args) == 1 { - depth = nodeAsIntValue(args[0], math.MaxUint) + depth = nodeAsUintValue(args[0], math.MaxUint) + } + var resSize checker.SizeEstimate + if (*target).Expr() != nil && (*target).Expr().Kind() == ast.ListKind { + szVal := estimateLiteralFlattenSize((*target).Expr(), depth) + resSize = checker.FixedSizeEstimate(szVal) + } else { + resSize = estimateFlattenSize(estimator, *target, depth) + } + cost := resSize.AsCost() + return estimateListCallWithDirectCost(cost, resSize, true) +} + +func estimateListFlattenLegacy(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if target == nil || len(args) > 1 { + return nil + } + depth := uint64(1) + if len(args) == 1 { + depth = nodeAsUintValue(args[0], math.MaxUint) } return estimateAllocatingListCall(float64(depth), estimateSize(estimator, *target)) } +func estimateFlattenSize(estimator checker.CostEstimator, node checker.AstNode, depth uint64) checker.SizeEstimate { + sz := estimateSize(estimator, node) + if depth == 0 { + return sz + } + tType := node.Type() + if tType.Kind() != types.ListKind || len(tType.Parameters()) == 0 { + return sz + } + elemType := tType.Parameters()[0] + elemNode := pathAstNode{ + path: append(append([]string(nil), node.Path()...), "@items"), + t: elemType, + } + flatElemSize := estimateFlattenSize(estimator, elemNode, depth-1) + return sz.Multiply(flatElemSize) +} + +func estimateLiteralFlattenSize(expr ast.Expr, depth uint64) uint64 { + if depth == 0 { + if expr.Kind() == ast.ListKind { + return uint64(expr.AsList().Size()) + } + return 1 + } + if expr.Kind() != ast.ListKind { + return 1 + } + listExpr := expr.AsList() + totalSize := uint64(0) + for _, el := range listExpr.Elements() { + totalSize += estimateLiteralFlattenSize(el, depth-1) + } + return totalSize +} + // Compute an O(n^2) with a cost factor of 2, equivalent to sets.contains with a result list // which can vary in size from 1 element to the original list size. func estimateListDistinct(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { @@ -656,8 +761,23 @@ func estimateListDistinct(estimator checker.CostEstimator, target *checker.AstNo return nil } sz := estimateSize(estimator, *target) - costFactor := 2.0 - return estimateAllocatingListCall(costFactor, sz.Multiply(sz)) + elemType := types.DynType + tType := (*target).Type() + if tType.Kind() == types.ListKind && len(tType.Parameters()) > 0 { + elemType = tType.Parameters()[0] + } + itemSize := estimateItemSize(estimator, *target) + elemCost := estimateElementEqualityCost(estimator, elemType, itemSize) + + costSize := sz.Multiply(sz) + cost := costSize.MultiplyByCost(elemCost).MultiplyByCostFactor(2.0) + + minSize := uint64(0) + if sz.Min > 0 { + minSize = 1 + } + resultSize := checker.SizeEstimate{Min: minSize, Max: sz.Max} + return estimateListCallWithDirectCost(cost, resultSize, true) } // estimateListSort computes an O(n^2) sort operation with a cost factor of 2 for the equality @@ -678,37 +798,53 @@ func estimateListSortBy(u *types.Type) checker.FunctionEstimator { if target == nil || len(args) != 1 { return nil } - // Estimate the size of the list used as the sort index - return estimateListSortCost(estimator, args[0], u) + // Estimate the size of the list used as the sort index, using target to resolve item size hints. + return estimateListSortByCost(estimator, *target, args[0], u) } } +func estimateListSortByCost(estimator checker.CostEstimator, target checker.AstNode, keysNode checker.AstNode, elemType *types.Type) *checker.CallEstimate { + sz := estimateSize(estimator, keysNode) + itemSize := estimateItemSize(estimator, target) + elemCost := estimateElementEqualityCost(estimator, elemType, itemSize) + + costSize := sz.Multiply(sz) + cost := costSize.MultiplyByCost(elemCost).MultiplyByCostFactor(2.0) + return estimateListCallWithDirectCost(cost, sz, true) +} + // estimateListSortCost estimates an O(n^2) sort operation with a cost factor of 2 for the equality // operations which occur during the sort computation. func estimateListSortCost(estimator checker.CostEstimator, node checker.AstNode, elemType *types.Type) *checker.CallEstimate { sz := estimateSize(estimator, node) - costFactor := 2.0 - switch elemType { - case types.StringType, types.BytesType: - costFactor += common.StringTraversalCostFactor - } - return estimateAllocatingListCall(costFactor, sz.Multiply(sz)) + itemSize := estimateItemSize(estimator, node) + elemCost := estimateElementEqualityCost(estimator, elemType, itemSize) + + costSize := sz.Multiply(sz) + cost := costSize.MultiplyByCost(elemCost).MultiplyByCostFactor(2.0) + return estimateListCallWithDirectCost(cost, sz, true) } // estimateAllocatingListCall computes cost as a function of the size of the result list with a // baseline cost for the call dispatch and the associated list allocation. func estimateAllocatingListCall(costFactor float64, listSize checker.SizeEstimate) *checker.CallEstimate { - return estimateListCall(costFactor, listSize, true) + return estimateListCallWithResultSize(costFactor, listSize, listSize, true) +} + +// estimateListCallWithResultSize computes cost as a function of the size of the target list and whether the +// call allocates memory, using a separate result size estimate for the output list. +func estimateListCallWithResultSize(costFactor float64, costSize checker.SizeEstimate, resultSize checker.SizeEstimate, allocates bool) *checker.CallEstimate { + cost := costSize.MultiplyByCostFactor(costFactor) + return estimateListCallWithDirectCost(cost, resultSize, allocates) } -// estimateListCall computes cost as a function of the size of the target list and whether the -// call allocates memory. -func estimateListCall(costFactor float64, listSize checker.SizeEstimate, allocates bool) *checker.CallEstimate { - cost := listSize.MultiplyByCostFactor(costFactor).Add(callCostEstimate) +// estimateListCallWithDirectCost computes cost using a pre-calculated CostEstimate and a separate result size estimate. +func estimateListCallWithDirectCost(cost checker.CostEstimate, resultSize checker.SizeEstimate, allocates bool) *checker.CallEstimate { if allocates { cost = cost.Add(checker.FixedCostEstimate(common.ListCreateBaseCost)) } - return &checker.CallEstimate{CostEstimate: cost, ResultSize: &listSize} + cost = cost.Add(callCostEstimate) + return &checker.CallEstimate{CostEstimate: cost, ResultSize: &resultSize} } // trackListOutputSize computes cost as a function of the size of the result list. @@ -716,9 +852,13 @@ func trackListOutputSize(_ []ref.Val, result ref.Val) *uint64 { return trackAllocatingListCall(1, actualSize(result)) } -// trackListFlatten computes cost as a function of the size of the result list and the depth of -// the flatten operation. -func trackListFlatten(args []ref.Val, _ ref.Val) *uint64 { +// trackListFlatten computes cost as a function of the size of the result list. +func trackListFlatten(args []ref.Val, result ref.Val) *uint64 { + resSize := actualSize(result) + return trackAllocatingListCall(1.0, resSize) +} + +func trackListFlattenLegacy(args []ref.Val, _ ref.Val) *uint64 { depth := 1.0 if len(args) == 2 { depth = float64(args[1].(types.Int)) @@ -753,27 +893,111 @@ func trackListSelfCompare(l traits.Lister) *uint64 { if elem.Type() == types.StringType || elem.Type() == types.BytesType { costFactor += common.StringTraversalCostFactor } - return trackAllocatingListCall(costFactor, sz*sz) + return trackAllocatingListCall(costFactor, safeMul(sz, sz)) } // trackAllocatingListCall computes costs as a function of the size of the result list with a baseline cost // for the call dispatch and the associated list allocation. func trackAllocatingListCall(costFactor float64, size uint64) *uint64 { - cost := uint64(float64(size)*costFactor) + callCost + common.ListCreateBaseCost + if costFactor < 0.0 { + costFactor = 1.0 + } + cost := safeAdd(uint64(float64(size)*costFactor), callCost, common.ListCreateBaseCost) return &cost } -func nodeAsIntValue(node checker.AstNode, defaultVal uint64) uint64 { - if node.Expr().Kind() != ast.LiteralKind { - return defaultVal +func estimateListDistinctLegacy(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if target == nil || len(args) != 0 { + return nil + } + sz := estimateSize(estimator, *target) + costFactor := 2.0 + tType := (*target).Type() + if tType.Kind() == types.ListKind && len(tType.Parameters()) > 0 { + elemType := tType.Parameters()[0] + if elemType.Kind() == types.StringKind || elemType.Kind() == types.BytesKind { + costFactor += common.StringTraversalCostFactor + } + } + return estimateAllocatingListCall(costFactor, sz.Multiply(sz)) +} + +func estimateListSortLegacy(t *types.Type) checker.FunctionEstimator { + return func(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if target == nil || len(args) != 0 { + return nil + } + return estimateListSortCostLegacy(estimator, *target, t) + } +} + +func estimateListSortByLegacy(u *types.Type) checker.FunctionEstimator { + return func(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if target == nil || len(args) != 1 { + return nil + } + return estimateListSortCostLegacy(estimator, args[0], u) + } +} + +func estimateListSortCostLegacy(estimator checker.CostEstimator, node checker.AstNode, elemType *types.Type) *checker.CallEstimate { + sz := estimateSize(estimator, node) + costFactor := 2.0 + switch elemType { + case types.StringType, types.BytesType: + costFactor += common.StringTraversalCostFactor } - lit := node.Expr().AsLiteral() - if lit.Type() != types.IntType { - return defaultVal + return estimateAllocatingListCall(costFactor, sz.Multiply(sz)) +} + +type pathAstNode struct { + path []string + t *types.Type +} + +func (p pathAstNode) Path() []string { + return p.path +} + +func (p pathAstNode) Type() *types.Type { + return p.t +} + +func (p pathAstNode) Expr() ast.Expr { + return nil +} + +func (p pathAstNode) ComputedSize() *checker.SizeEstimate { + return nil +} + +func estimateItemSize(estimator checker.CostEstimator, node checker.AstNode) checker.SizeEstimate { + path := node.Path() + if len(path) == 0 { + return checker.SizeEstimate{Min: 0, Max: math.MaxUint64} + } + elemType := types.DynType + tType := node.Type() + if tType.Kind() == types.ListKind && len(tType.Parameters()) > 0 { + elemType = tType.Parameters()[0] + } + itemNode := pathAstNode{ + path: append(append([]string(nil), path...), "@items"), + t: elemType, } - val := lit.(types.Int) - if val < types.IntZero { - return 0 + if l := estimator.EstimateSize(itemNode); l != nil { + return *l + } + return checker.SizeEstimate{Min: 0, Max: math.MaxUint64} +} + +func estimateElementEqualityCost(estimator checker.CostEstimator, elemType *types.Type, itemSize checker.SizeEstimate) checker.CostEstimate { + switch elemType.Kind() { + case types.StringKind, types.BytesKind: + return itemSize.MultiplyByCostFactor(common.StringTraversalCostFactor) + case types.ListKind, types.MapKind, types.StructKind: + return checker.UnknownCostEstimate() + default: + return checker.FixedCostEstimate(1) } - return uint64(lit.(types.Int)) } diff --git a/vendor/github.com/google/cel-go/ext/math.go b/vendor/github.com/google/cel-go/ext/math.go index 6df8e3773..e67b205de 100644 --- a/vendor/github.com/google/cel-go/ext/math.go +++ b/vendor/github.com/google/cel-go/ext/math.go @@ -20,10 +20,12 @@ import ( "strings" "github.com/google/cel-go/cel" + "github.com/google/cel-go/checker" "github.com/google/cel-go/common/ast" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" "github.com/google/cel-go/common/types/traits" + "github.com/google/cel-go/interpreter" ) // Math returns a cel.EnvOption to configure namespaced math helper macros and @@ -339,9 +341,9 @@ import ( // // Examples: // -// math.sqrt(81) // returns 9.0 -// math.sqrt(985.25) // returns 31.388692231439016 -// math.sqrt(-15) // returns NaN +// math.sqrt(81) // returns 9.0 +// math.sqrt(985.25) // returns 31.388692231439016 +// math.sqrt(-15) // returns NaN func Math(options ...MathOption) cel.EnvOption { m := &mathLib{version: math.MaxUint32} for _, o := range options { @@ -580,12 +582,35 @@ func (lib *mathLib) CompileOptions() []cel.EnvOption { ), ) } + if lib.version >= 3 { + estimators := []checker.CostOption{ + checker.OverloadCostEstimate("math_@min_list_double", estimateMathListCost), + checker.OverloadCostEstimate("math_@min_list_int", estimateMathListCost), + checker.OverloadCostEstimate("math_@min_list_uint", estimateMathListCost), + checker.OverloadCostEstimate("math_@max_list_double", estimateMathListCost), + checker.OverloadCostEstimate("math_@max_list_int", estimateMathListCost), + checker.OverloadCostEstimate("math_@max_list_uint", estimateMathListCost), + } + opts = append(opts, cel.CostEstimatorOptions(estimators...)) + } return opts } // ProgramOptions implements the Library interface method. -func (*mathLib) ProgramOptions() []cel.ProgramOption { - return []cel.ProgramOption{} +func (lib *mathLib) ProgramOptions() []cel.ProgramOption { + var opts []cel.ProgramOption + if lib.version >= 3 { + trackers := []interpreter.CostTrackerOption{ + interpreter.OverloadCostTracker("math_@min_list_double", trackMathListCost), + interpreter.OverloadCostTracker("math_@min_list_int", trackMathListCost), + interpreter.OverloadCostTracker("math_@min_list_uint", trackMathListCost), + interpreter.OverloadCostTracker("math_@max_list_double", trackMathListCost), + interpreter.OverloadCostTracker("math_@max_list_int", trackMathListCost), + interpreter.OverloadCostTracker("math_@max_list_uint", trackMathListCost), + } + opts = append(opts, cel.CostTrackerOptions(trackers...)) + } + return opts } func mathLeast(meh cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *cel.Error) { @@ -723,21 +748,19 @@ func sign(val ref.Val) ref.Val { } } - func sqrt(val ref.Val) ref.Val { switch v := val.(type) { case types.Double: - return types.Double(math.Sqrt(float64(v))) + return types.Double(math.Sqrt(float64(v))) case types.Int: - return types.Double(math.Sqrt(float64(v))) + return types.Double(math.Sqrt(float64(v))) case types.Uint: - return types.Double(math.Sqrt(float64(v))) + return types.Double(math.Sqrt(float64(v))) default: - return types.NewErr("no such overload: sqrt") + return types.NewErr("no such overload: sqrt") } } - func bitAndPairInt(first, second ref.Val) ref.Val { l := first.(types.Int) r := second.(types.Int) @@ -946,3 +969,19 @@ func maybeSuffixError(val ref.Val, suffix string) ref.Val { } return val } + +func estimateMathListCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if len(args) != 1 { + return nil + } + sz := estimateSize(estimator, args[0]) + cost := sz.MultiplyByCostFactor(1.0).Add(callCostEstimate) + resultSize := checker.FixedSizeEstimate(1) + return &checker.CallEstimate{CostEstimate: cost, ResultSize: &resultSize} +} + +func trackMathListCost(args []ref.Val, _ ref.Val) *uint64 { + sz := actualSize(args[0]) + cost := safeAdd(sz, callCost) + return &cost +} diff --git a/vendor/github.com/google/cel-go/ext/native.go b/vendor/github.com/google/cel-go/ext/native.go index 661984cbb..2598b38d6 100644 --- a/vendor/github.com/google/cel-go/ext/native.go +++ b/vendor/github.com/google/cel-go/ext/native.go @@ -15,31 +15,38 @@ package ext import ( - "errors" - "fmt" - "math" - "reflect" - "strings" - "time" - - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoreflect" - "github.com/google/cel-go/cel" "github.com/google/cel-go/common/types" - "github.com/google/cel-go/common/types/pb" - "github.com/google/cel-go/common/types/ref" - "github.com/google/cel-go/common/types/traits" - - structpb "google.golang.org/protobuf/types/known/structpb" ) +// NativeTypesOption is a functional interface for configuring handling of native types. +type NativeTypesOption = types.NativeTypeOption + +// 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. +type NativeTypesFieldNameHandler = types.NativeTypesFieldNameHandler + var ( - nativeObjTraitMask = traits.FieldTesterType | traits.IndexerType - jsonValueType = reflect.TypeOf(&structpb.Value{}) - jsonStructType = reflect.TypeOf(&structpb.Struct{}) + // ParseStructTags configures if native types field names should be overridable by CEL struct tags. + // This is equivalent to ParseStructTag("cel") + ParseStructTags = types.ParseStructTags + + // ParseStructTag configures the struct tag to parse. The 0th item in the tag is used as the name of the CEL field. + ParseStructTag = types.ParseStructTag + + // ParseStructField configures how to parse Go struct fields. It can be used to customize struct field parsing. + ParseStructField = types.ParseStructField ) +// NativeTypesVersion sets the native types version support for native extensions functions. +// +// Deprecated: NativeTypesVersion is a no-op and will be removed in a future release. +func NativeTypesVersion(version uint32) NativeTypesOption { + return func(*types.NativeTypeOptions) error { + return nil + } +} + // NativeTypes creates a type provider which uses reflect.Type and reflect.Value instances // to produce type definitions that can be used within CEL. // @@ -98,699 +105,14 @@ var ( // In case there are duplicated field names in the struct, an error will be returned. func NativeTypes(args ...any) cel.EnvOption { return func(env *cel.Env) (*cel.Env, error) { - nativeTypes := make([]any, 0, len(args)) - tpOptions := nativeTypeOptions{ - version: math.MaxUint32, - } - - for _, v := range args { - switch v := v.(type) { - case NativeTypesOption: - err := v(&tpOptions) - if err != nil { - return nil, err - } - default: - nativeTypes = append(nativeTypes, v) - } - } - - tp, err := newNativeTypeProvider(tpOptions, env.CELTypeAdapter(), env.CELTypeProvider(), nativeTypes...) + p, a, err := types.ComposeTypes(env.CELTypeProvider(), env.CELTypeAdapter(), args...) if err != nil { return nil, err } - - env, err = cel.CustomTypeAdapter(tp)(env) + env, err = cel.CustomTypeAdapter(a)(env) if err != nil { return nil, err } - return cel.CustomTypeProvider(tp)(env) - } -} - -// NativeTypesOption is a functional interface for configuring handling of native types. -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. -// This can be used to override the default Go struct field to CEL field name mapping. -type NativeTypesFieldNameHandler = func(field reflect.StructField) string - -func fieldNameByTag(structTagToParse string) func(field reflect.StructField) string { - return func(field reflect.StructField) string { - tag, found := field.Tag.Lookup(structTagToParse) - if found { - splits := strings.Split(tag, ",") - if len(splits) > 0 { - // We make the assumption that the leftmost entry in the tag is the name. - // This seems to be true for most tags that have the concept of a name/key, such as: - // https://pkg.go.dev/encoding/xml#Marshal - // https://pkg.go.dev/encoding/json#Marshal - // https://pkg.go.dev/go.mongodb.org/mongo-driver/bson#hdr-Structs - // https://pkg.go.dev/gopkg.in/yaml.v2#Marshal - name := splits[0] - return name - } - } - - return field.Name - } -} - -type nativeTypeOptions struct { - // fieldNameHandler controls how CEL should perform struct field renames. - // This is most commonly used for switching to parsing based off the struct field tag, - // such as "cel" or "json". - 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. -// This is equivalent to ParseStructTag("cel") -func ParseStructTags(enabled bool) NativeTypesOption { - return func(ntp *nativeTypeOptions) error { - if enabled { - ntp.fieldNameHandler = fieldNameByTag("cel") - } else { - ntp.fieldNameHandler = nil - } - return nil - } -} - -// ParseStructTag configures the struct tag to parse. The 0th item in the tag is used as the name of the CEL field. -// For example: -// If the tag to parse is "cel" and the struct field has tag cel:"foo", the CEL struct field will be "foo". -// If the tag to parse is "json" and the struct field has tag json:"foo,omitempty", the CEL struct field will be "foo". -func ParseStructTag(tag string) NativeTypesOption { - return func(ntp *nativeTypeOptions) error { - ntp.fieldNameHandler = fieldNameByTag(tag) - return nil - } -} - -// ParseStructField configures how to parse Go struct fields. It can be used to customize struct field parsing. -func ParseStructField(handler NativeTypesFieldNameHandler) NativeTypesOption { - return func(ntp *nativeTypeOptions) error { - ntp.fieldNameHandler = handler - return nil - } -} - -func newNativeTypeProvider(tpOptions nativeTypeOptions, adapter types.Adapter, provider types.Provider, refTypes ...any) (*nativeTypeProvider, error) { - nativeTypes := make(map[string]*nativeType, len(refTypes)) - for _, refType := range refTypes { - switch rt := refType.(type) { - case reflect.Type: - result, err := newNativeTypes(tpOptions.fieldNameHandler, rt) - if err != nil { - return nil, err - } - for idx := range result { - nativeTypes[result[idx].TypeName()] = result[idx] - } - case reflect.Value: - result, err := newNativeTypes(tpOptions.fieldNameHandler, rt.Type()) - if err != nil { - return nil, err - } - for idx := range result { - nativeTypes[result[idx].TypeName()] = result[idx] - } - default: - return nil, fmt.Errorf("unsupported native type: %v (%T) must be reflect.Type or reflect.Value", rt, rt) - } - } - return &nativeTypeProvider{ - nativeTypes: nativeTypes, - baseAdapter: adapter, - baseProvider: provider, - options: tpOptions, - }, nil -} - -type nativeTypeProvider struct { - nativeTypes map[string]*nativeType - baseAdapter types.Adapter - baseProvider types.Provider - options nativeTypeOptions -} - -// EnumValue proxies to the types.Provider configured at the times the NativeTypes -// option was configured. -func (tp *nativeTypeProvider) EnumValue(enumName string) ref.Val { - return tp.baseProvider.EnumValue(enumName) -} - -// FindIdent looks up natives type instances by qualified identifier, and if not found -// proxies to the composed types.Provider. -func (tp *nativeTypeProvider) FindIdent(typeName string) (ref.Val, bool) { - if t, found := tp.nativeTypes[typeName]; found { - return t, true - } - return tp.baseProvider.FindIdent(typeName) -} - -// FindStructType looks up the CEL type definition by qualified identifier, and if not found -// proxies to the composed types.Provider. -func (tp *nativeTypeProvider) FindStructType(typeName string) (*types.Type, bool) { - if _, found := tp.nativeTypes[typeName]; found { - return types.NewTypeTypeWithParam(types.NewObjectType(typeName)), true - } - if celType, found := tp.baseProvider.FindStructType(typeName); found { - return celType, true - } - return tp.baseProvider.FindStructType(typeName) -} - -func toFieldName(fieldNameHandler NativeTypesFieldNameHandler, f reflect.StructField) string { - if fieldNameHandler == nil { - return f.Name - } - - return fieldNameHandler(f) -} - -// FindStructFieldNames looks up the type definition first from the native types, then from -// the backing provider type set. If found, a set of field names corresponding to the type -// will be returned. -func (tp *nativeTypeProvider) FindStructFieldNames(typeName string) ([]string, bool) { - if t, found := tp.nativeTypes[typeName]; found { - fieldCount := t.refType.NumField() - fields := make([]string, fieldCount) - for i := 0; i < fieldCount; i++ { - fields[i] = toFieldName(tp.options.fieldNameHandler, t.refType.Field(i)) - } - return fields, true - } - if celTypeFields, found := tp.baseProvider.FindStructFieldNames(typeName); found { - return celTypeFields, true - } - return tp.baseProvider.FindStructFieldNames(typeName) -} - -// FindStructFieldType looks up a native type's field definition, and if the type name is not a native -// type then proxies to the composed types.Provider -func (tp *nativeTypeProvider) FindStructFieldType(typeName, fieldName string) (*types.FieldType, bool) { - t, found := tp.nativeTypes[typeName] - if !found { - return tp.baseProvider.FindStructFieldType(typeName, fieldName) - } - refField, isDefined := t.hasField(fieldName) - if !found || !isDefined { - return nil, false - } - celType, ok := convertToCelType(refField.Type) - if !ok { - return nil, false - } - return &types.FieldType{ - Type: celType, - IsSet: func(obj any) bool { - refVal := reflect.Indirect(reflect.ValueOf(obj)) - refField := refVal.FieldByName(refField.Name) - return !refField.IsZero() - }, - GetFrom: func(obj any) (any, error) { - refVal := reflect.Indirect(reflect.ValueOf(obj)) - refField := refVal.FieldByName(refField.Name) - return getFieldValue(refField), nil - }, - }, true -} - -// NewValue implements the ref.TypeProvider interface method. -func (tp *nativeTypeProvider) NewValue(typeName string, fields map[string]ref.Val) ref.Val { - t, found := tp.nativeTypes[typeName] - if !found { - return tp.baseProvider.NewValue(typeName, fields) - } - refPtr := reflect.New(t.refType) - refVal := refPtr.Elem() - for fieldName, val := range fields { - refFieldDef, isDefined := t.hasField(fieldName) - if !isDefined { - return types.NewErr("no such field: %s", fieldName) - } - fieldVal, err := val.ConvertToNative(refFieldDef.Type) - if err != nil { - return types.NewErrFromString(err.Error()) - } - refField := refVal.FieldByIndex(refFieldDef.Index) - refFieldVal := reflect.ValueOf(fieldVal) - refField.Set(refFieldVal) - } - return tp.NativeToValue(refPtr.Interface()) -} - -// NewValue adapts native values to CEL values and will proxy to the composed type adapter -// for non-native types. -func (tp *nativeTypeProvider) NativeToValue(val any) ref.Val { - if val == nil { - return types.NullValue - } - if v, ok := val.(ref.Val); ok { - return v - } - rawVal := reflect.ValueOf(val) - refVal := rawVal - if refVal.Kind() == reflect.Ptr { - refVal = reflect.Indirect(refVal) - } - // This isn't quite right if you're also supporting proto, - // but maybe an acceptable limitation. - switch refVal.Kind() { - case reflect.Array, reflect.Slice: - switch val := val.(type) { - case []byte: - return tp.baseAdapter.NativeToValue(val) - default: - if refVal.Type().Elem() == reflect.TypeOf(byte(0)) { - return tp.baseAdapter.NativeToValue(val) - } - return types.NewDynamicList(tp, val) - } - case reflect.Map: - return types.NewDynamicMap(tp, val) - case reflect.Struct: - switch val := val.(type) { - case proto.Message, *pb.Map, protoreflect.List, protoreflect.Message, protoreflect.Value, - time.Time: - return tp.baseAdapter.NativeToValue(val) - default: - return tp.newNativeObject(val, rawVal) - } - default: - return tp.baseAdapter.NativeToValue(val) - } -} - -func convertToCelType(refType reflect.Type) (*cel.Type, bool) { - switch refType.Kind() { - case reflect.Bool: - return cel.BoolType, true - case reflect.Float32, reflect.Float64: - return cel.DoubleType, true - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - if refType == durationType { - return cel.DurationType, true - } - return cel.IntType, true - case reflect.String: - return cel.StringType, true - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return cel.UintType, true - case reflect.Array, reflect.Slice: - refElem := refType.Elem() - if refElem == reflect.TypeOf(byte(0)) { - return cel.BytesType, true - } - elemType, ok := convertToCelType(refElem) - if !ok { - return nil, false - } - return cel.ListType(elemType), true - case reflect.Map: - keyType, ok := convertToCelType(refType.Key()) - if !ok { - return nil, false - } - // Ensure the key type is a int, bool, uint, string - elemType, ok := convertToCelType(refType.Elem()) - if !ok { - return nil, false - } - return cel.MapType(keyType, elemType), true - case reflect.Struct: - if refType == timestampType { - return cel.TimestampType, true - } - return cel.ObjectType( - fmt.Sprintf("%s.%s", simplePkgAlias(refType.PkgPath()), refType.Name()), - ), true - case reflect.Pointer: - if refType.Implements(pbMsgInterfaceType) { - pbMsg := reflect.New(refType.Elem()).Interface().(protoreflect.ProtoMessage) - return cel.ObjectType(string(pbMsg.ProtoReflect().Descriptor().FullName())), true - } - return convertToCelType(refType.Elem()) - } - return nil, false -} - -func (tp *nativeTypeProvider) newNativeObject(val any, refValue reflect.Value) ref.Val { - valType, err := newNativeType(tp.options.fieldNameHandler, refValue.Type()) - if err != nil { - return types.NewErrFromString(err.Error()) - } - return &nativeObj{ - Adapter: tp, - val: val, - valType: valType, - refValue: refValue, + return cel.CustomTypeProvider(p)(env) } } - -type nativeObj struct { - types.Adapter - val any - valType *nativeType - refValue reflect.Value -} - -// ConvertToNative implements the ref.Val interface method. -// -// CEL does not have a notion of pointers, so whether a field is a pointer or value -// is handled as part of this conversion step. -func (o *nativeObj) ConvertToNative(typeDesc reflect.Type) (any, error) { - if o.refValue.Type() == typeDesc { - return o.val, nil - } - if o.refValue.Kind() == reflect.Pointer && o.refValue.Type().Elem() == typeDesc { - return o.refValue.Elem().Interface(), nil - } - if typeDesc.Kind() == reflect.Pointer && o.refValue.Type() == typeDesc.Elem() { - ptr := reflect.New(typeDesc.Elem()) - ptr.Elem().Set(o.refValue) - return ptr.Interface(), nil - } - switch typeDesc { - case jsonValueType: - jsonStruct, err := o.ConvertToNative(jsonStructType) - if err != nil { - return nil, err - } - return structpb.NewStructValue(jsonStruct.(*structpb.Struct)), nil - case jsonStructType: - refVal := reflect.Indirect(o.refValue) - refType := refVal.Type() - fields := make(map[string]*structpb.Value, refVal.NumField()) - for i := 0; i < refVal.NumField(); i++ { - fieldType := refType.Field(i) - fieldValue := refVal.Field(i) - if !fieldValue.IsValid() || fieldValue.IsZero() { - continue - } - fieldName := toFieldName(o.valType.fieldNameHandler, fieldType) - fieldCELVal := o.NativeToValue(fieldValue.Interface()) - fieldJSONVal, err := fieldCELVal.ConvertToNative(jsonValueType) - if err != nil { - return nil, err - } - fields[fieldName] = fieldJSONVal.(*structpb.Value) - } - return &structpb.Struct{Fields: fields}, nil - } - return nil, fmt.Errorf("type conversion error from '%v' to '%v'", o.Type(), typeDesc) -} - -// ConvertToType implements the ref.Val interface method. -func (o *nativeObj) ConvertToType(typeVal ref.Type) ref.Val { - switch typeVal { - case types.TypeType: - return o.valType - default: - if typeVal.TypeName() == o.valType.typeName { - return o - } - } - return types.NewErr("type conversion error from '%s' to '%s'", o.Type(), typeVal) -} - -// Equal implements the ref.Val interface method. -// -// Note, that in Golang a pointer to a value is not equal to the value it contains. -// In CEL pointers and values to which they point are equal. -func (o *nativeObj) Equal(other ref.Val) ref.Val { - otherNtv, ok := other.(*nativeObj) - if !ok { - return types.False - } - val := o.val - otherVal := otherNtv.val - refVal := o.refValue - otherRefVal := otherNtv.refValue - if refVal.Kind() != otherRefVal.Kind() { - if refVal.Kind() == reflect.Pointer { - val = refVal.Elem().Interface() - } else if otherRefVal.Kind() == reflect.Pointer { - otherVal = otherRefVal.Elem().Interface() - } - } - return types.Bool(reflect.DeepEqual(val, otherVal)) -} - -// IsZeroValue indicates whether the contained Golang value is a zero value. -// -// Golang largely follows proto3 semantics for zero values. -func (o *nativeObj) IsZeroValue() bool { - return reflect.Indirect(o.refValue).IsZero() -} - -// IsSet tests whether a field which is defined is set to a non-default value. -func (o *nativeObj) IsSet(field ref.Val) ref.Val { - refField, refErr := o.getReflectedField(field) - if refErr != nil { - return refErr - } - return types.Bool(!refField.IsZero()) -} - -// Get returns the value fo a field name. -func (o *nativeObj) Get(field ref.Val) ref.Val { - refField, refErr := o.getReflectedField(field) - if refErr != nil { - return refErr - } - return adaptFieldValue(o, refField) -} - -func (o *nativeObj) getReflectedField(field ref.Val) (reflect.Value, ref.Val) { - fieldName, ok := field.(types.String) - if !ok { - return reflect.Value{}, types.MaybeNoSuchOverloadErr(field) - } - fieldNameStr := string(fieldName) - refField, isDefined := o.valType.hasField(fieldNameStr) - if !isDefined { - return reflect.Value{}, types.NewErr("no such field: %s", fieldName) - } - refVal := reflect.Indirect(o.refValue) - return refVal.FieldByIndex(refField.Index), nil -} - -// Type implements the ref.Val interface method. -func (o *nativeObj) Type() ref.Type { - return o.valType -} - -// Value implements the ref.Val interface method. -func (o *nativeObj) Value() any { - return o.val -} - -func newNativeTypes(fieldNameHandler NativeTypesFieldNameHandler, rawType reflect.Type) ([]*nativeType, error) { - nt, err := newNativeType(fieldNameHandler, rawType) - if err != nil { - return nil, err - } - result := []*nativeType{nt} - - alreadySeen := make(map[string]struct{}) - var iterateStructMembers func(reflect.Type) - iterateStructMembers = func(t reflect.Type) { - if k := t.Kind(); k == reflect.Pointer || k == reflect.Slice || k == reflect.Array || k == reflect.Map { - t = t.Elem() - } - if t.Kind() != reflect.Struct { - return - } - if _, seen := alreadySeen[t.String()]; seen { - return - } - alreadySeen[t.String()] = struct{}{} - nt, ntErr := newNativeType(fieldNameHandler, t) - if ntErr != nil { - err = ntErr - return - } - result = append(result, nt) - - for idx := 0; idx < t.NumField(); idx++ { - iterateStructMembers(t.Field(idx).Type) - } - } - iterateStructMembers(rawType) - - return result, err -} - -var ( - errDuplicatedFieldName = errors.New("field name already exists in struct") -) - -func newNativeType(fieldNameHandler NativeTypesFieldNameHandler, rawType reflect.Type) (*nativeType, error) { - refType := rawType - if refType.Kind() == reflect.Pointer { - refType = refType.Elem() - } - if !isValidObjectType(refType) { - return nil, fmt.Errorf("unsupported reflect.Type %v, must be reflect.Struct", rawType) - } - - // Since naming collisions can only happen with struct tag parsing, we only check for them if it is enabled. - if fieldNameHandler != nil { - fieldNames := make(map[string]struct{}) - - for idx := 0; idx < refType.NumField(); idx++ { - field := refType.Field(idx) - fieldName := toFieldName(fieldNameHandler, field) - - if _, found := fieldNames[fieldName]; found { - return nil, fmt.Errorf("invalid field name `%s` in struct `%s`: %w", fieldName, refType.Name(), errDuplicatedFieldName) - } else { - fieldNames[fieldName] = struct{}{} - } - } - } - - return &nativeType{ - typeName: fmt.Sprintf("%s.%s", simplePkgAlias(refType.PkgPath()), refType.Name()), - refType: refType, - fieldNameHandler: fieldNameHandler, - }, nil -} - -type nativeType struct { - typeName string - refType reflect.Type - fieldNameHandler NativeTypesFieldNameHandler -} - -// ConvertToNative implements ref.Val.ConvertToNative. -func (t *nativeType) ConvertToNative(typeDesc reflect.Type) (any, error) { - return nil, fmt.Errorf("type conversion error for type to '%v'", typeDesc) -} - -// ConvertToType implements ref.Val.ConvertToType. -func (t *nativeType) ConvertToType(typeVal ref.Type) ref.Val { - switch typeVal { - case types.TypeType: - return types.TypeType - } - return types.NewErr("type conversion error from '%s' to '%s'", types.TypeType, typeVal) -} - -// Equal returns true of both type names are equal to each other. -func (t *nativeType) Equal(other ref.Val) ref.Val { - otherType, ok := other.(ref.Type) - return types.Bool(ok && t.TypeName() == otherType.TypeName()) -} - -// HasTrait implements the ref.Type interface method. -func (t *nativeType) HasTrait(trait int) bool { - return nativeObjTraitMask&trait == trait -} - -// String implements the strings.Stringer interface method. -func (t *nativeType) String() string { - return t.typeName -} - -// Type implements the ref.Val interface method. -func (t *nativeType) Type() ref.Type { - return types.TypeType -} - -// TypeName implements the ref.Type interface method. -func (t *nativeType) TypeName() string { - return t.typeName -} - -// Value implements the ref.Val interface method. -func (t *nativeType) Value() any { - return t.typeName -} - -// fieldByName returns the corresponding reflect.StructField for the give name either by matching -// field tag or field name. -func (t *nativeType) fieldByName(fieldName string) (reflect.StructField, bool) { - if t.fieldNameHandler == nil { - return t.refType.FieldByName(fieldName) - } - - for i := 0; i < t.refType.NumField(); i++ { - f := t.refType.Field(i) - if toFieldName(t.fieldNameHandler, f) == fieldName { - return f, true - } - } - - return reflect.StructField{}, false -} - -// hasField returns whether a field name has a corresponding Golang reflect.StructField -func (t *nativeType) hasField(fieldName string) (reflect.StructField, bool) { - f, found := t.fieldByName(fieldName) - if !found || !f.IsExported() || !isSupportedType(f.Type) { - return reflect.StructField{}, false - } - return f, true -} - -func adaptFieldValue(adapter types.Adapter, refField reflect.Value) ref.Val { - return adapter.NativeToValue(getFieldValue(refField)) -} - -func getFieldValue(refField reflect.Value) any { - if refField.IsZero() { - switch refField.Kind() { - case reflect.Struct: - if refField.Type() == timestampType { - return time.Unix(0, 0) - } - case reflect.Pointer: - return reflect.New(refField.Type().Elem()).Interface() - } - } - return refField.Interface() -} - -func simplePkgAlias(pkgPath string) string { - paths := strings.Split(pkgPath, "/") - if len(paths) == 0 { - return "" - } - return paths[len(paths)-1] -} - -func isValidObjectType(refType reflect.Type) bool { - return refType.Kind() == reflect.Struct -} - -func isSupportedType(refType reflect.Type) bool { - switch refType.Kind() { - case reflect.Chan, reflect.Complex64, reflect.Complex128, reflect.Func, reflect.UnsafePointer, reflect.Uintptr: - return false - case reflect.Array, reflect.Slice: - return isSupportedType(refType.Elem()) - case reflect.Map: - return isSupportedType(refType.Key()) && isSupportedType(refType.Elem()) - } - return true -} - -var ( - pbMsgInterfaceType = reflect.TypeOf((*protoreflect.ProtoMessage)(nil)).Elem() - timestampType = reflect.TypeOf(time.Now()) - durationType = reflect.TypeOf(time.Nanosecond) -) diff --git a/vendor/github.com/google/cel-go/ext/network.go b/vendor/github.com/google/cel-go/ext/network.go new file mode 100644 index 000000000..bca065707 --- /dev/null +++ b/vendor/github.com/google/cel-go/ext/network.go @@ -0,0 +1,810 @@ +// Copyright 2025 Google LLC +// +// 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 ext + +import ( + "fmt" + "math" + "net/netip" + "reflect" + + "github.com/google/cel-go/cel" + "github.com/google/cel-go/checker" + "github.com/google/cel-go/common/ast" + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" + "github.com/google/cel-go/interpreter" +) + +const ( + // Version1 is the initial version of the Network library, providing + // parity with Kubernetes v1.30+ CEL network functions. + Version1 uint32 = 1 +) + +// Network returns a cel.EnvOption to configure extended functions for network +// address parsing, inspection, and CIDR range manipulation. +// +// Note: This library defines global functions `ip`, `cidr`, `isIP`, `isCIDR` +// and `ip.isCanonical`. If you are currently using variables named `ip` or +// `cidr`, these functions will likely work as intended, however there is a +// chance for collision. +// +// The library closely mirrors the behavior of the Kubernetes CEL network +// libraries, treating IP addresses and CIDR ranges as opaque types. It parses +// IPs strictly: IPv4-mapped IPv6 addresses and IP zones are not allowed. +// +// This library includes a TypeAdapter that allows `netip.Addr` and +// `netip.Prefix` Go types to be passed directly into the CEL environment. +// +// # IP Addresses +// +// The `ip` function converts a string to an IP address (IPv4 or IPv6). If the +// string is not a valid IP, an error is returned. The `isIP` function checks +// if a string is a valid IP address without throwing an error. +// +// ip(string) -> ip +// isIP(string) -> bool +// +// Examples: +// +// ip('127.0.0.1') +// ip('::1') +// isIP('1.2.3.4') // true +// isIP('invalid') // false +// +// # CIDR Ranges +// +// The `cidr` function converts a string to a Classless Inter-Domain Routing +// (CIDR) range. If the string is not valid, an error is returned. +// +// The `isCIDR` function checks if a string is a valid CIDR notation. Note that +// `isCIDR` allows CIDR values with or without host bits (e.g., '10.0.0.1/8' +// or '10.0.0.0/8'). +// +// cidr(string) -> cidr +// isCIDR(string) -> bool +// +// Examples: +// +// cidr('192.168.0.0/24') +// cidr('::1/128') +// isCIDR('10.0.0.0/8') // true +// isCIDR('10.0.0.1/8') // true +// +// # IP Inspection and Canonicalization +// +// IP objects support various inspection methods. +// +// .family() -> int +// .isLoopback() -> bool +// .isGlobalUnicast() -> bool +// .isLinkLocalMulticast() -> bool +// .isLinkLocalUnicast() -> bool +// .isUnspecified() -> bool +// +// The `ip.isCanonical` function takes a string and returns true if it matches +// the RFC 5952 canonical string representation of that address. +// +// ip.isCanonical(string) -> bool +// +// Examples: +// +// ip('127.0.0.1').family() == 4 +// ip('::1').family() == 6 +// ip('127.0.0.1').isLoopback() == true +// ip.isCanonical('2001:db8::1') == true // RFC 5952 format +// ip.isCanonical('2001:DB8::1') == false // Uppercase is not canonical +// ip.isCanonical('2001:db8:0:0:0:0:0:1') == false // Expanded is not canonical +// +// # CIDR Member Functions +// +// CIDR objects support containment checks and property extraction. +// +// .containsIP(ip|string) -> bool +// .containsCIDR(cidr|string) -> bool +// .ip() -> ip +// .isMask() -> bool +// .masked() -> cidr +// .prefixLength() -> int +// +// Examples: +// +// cidr('10.0.0.0/8').containsIP(ip('10.0.0.1')) == true +// cidr('10.0.0.0/8').containsIP('10.0.0.1') == true +// cidr('10.0.0.0/8').containsCIDR('10.1.0.0/16') == true +// cidr('192.168.1.5/24').ip() == ip('192.168.1.5') +// cidr('192.168.1.0/24').isMask() == true +// cidr('192.168.1.5/24').isMask() == false +// cidr('192.168.1.5/24').masked() == cidr('192.168.1.0/24') +// cidr('192.168.1.0/24').prefixLength() == 24 +func Network(opts ...NetworkOption) cel.EnvOption { + lib := &networkLib{version: Version1} + for _, o := range opts { + lib = o(lib) + } + return func(e *cel.Env) (*cel.Env, error) { + // Install the library (Types and Functions) + e, err := cel.Lib(lib)(e) + if err != nil { + return nil, err + } + + // Install the Adapter (Wrapping the existing one) + adapter := &networkAdapter{Adapter: e.CELTypeAdapter()} + return cel.CustomTypeAdapter(adapter)(e) + } +} + +// NetworkOption declares a functional operator for configuring the Network library behavior. +type NetworkOption func(*networkLib) *networkLib + +// NetworkVersion sets the version of the network library to an explicit version. +func NetworkVersion(version uint32) NetworkOption { + return func(lib *networkLib) *networkLib { + lib.version = version + return lib + } +} + +const ( + // Function names matching the original Kubernetes implementation of this networking library. + // isStrictCIDR and isInterfaceAddress are added to enable strict isCIDR parsing without breaking + // functionality for existing users. Ctx: https://github.com/kubernetes/kubernetes/issues/134224 + cidrFunc = "cidr" + cidrToString = "string" + containsCIDRFunc = "containsCIDR" + containsIPFunc = "containsIP" + familyFunc = "family" + ipFunc = "ip" + ipToString = "string" + isCanonicalFunc = "ip.isCanonical" + isCIDRFunc = "isCIDR" + isGlobalUnicastFunc = "isGlobalUnicast" + isIPFunc = "isIP" + isLinkLocalMcastFunc = "isLinkLocalMulticast" + isLinkLocalUcastFunc = "isLinkLocalUnicast" + isLoopbackFunc = "isLoopback" + isMaskFunc = "isMask" + isUnspecifiedFunc = "isUnspecified" + maskedFunc = "masked" + prefixLengthFunc = "prefixLength" +) + +var ( + // Definitions for the Opaque Types + + // IPType represents a network IP address. + IPType = types.NewOpaqueType("net.IP") + + // CIDRType represents a CIDR-format network range. + CIDRType = types.NewOpaqueType("net.CIDR") +) + +type networkLib struct { + version uint32 +} + +func (*networkLib) LibraryName() string { + return "cel.lib.ext.network" +} + +func (*networkLib) CompileOptions() []cel.EnvOption { + return []cel.EnvOption{ + cel.Types( + IPType, + CIDRType, + ), + + cel.Function(cidrFunc, + // K8s Parity: Following the pattern, this is "string_to_cidr" + cel.Overload("string_to_cidr", []*cel.Type{cel.StringType}, CIDRType, + cel.UnaryBinding(netCIDRString)), + ), + cel.Function(cidrToString, + cel.Overload("cidr_to_string", []*cel.Type{CIDRType}, cel.StringType, + cel.UnaryBinding(netCIDRToString)), + ), + cel.Function(containsCIDRFunc, + cel.MemberOverload("cidr_contains_cidr", []*cel.Type{CIDRType, CIDRType}, cel.BoolType, + cel.BinaryBinding(netCIDRContainsCIDR)), + cel.MemberOverload("cidr_contains_cidr_string", []*cel.Type{CIDRType, cel.StringType}, cel.BoolType, + cel.BinaryBinding(netCIDRContainsCIDRString)), + ), + cel.Function(containsIPFunc, + cel.MemberOverload("cidr_contains_ip_ip", []*cel.Type{CIDRType, IPType}, cel.BoolType, + cel.BinaryBinding(netCIDRContainsIP)), + cel.MemberOverload("cidr_contains_ip_string", []*cel.Type{CIDRType, cel.StringType}, cel.BoolType, + cel.BinaryBinding(netCIDRContainsIPString)), + ), + cel.Function(familyFunc, + cel.MemberOverload("ip_family", []*cel.Type{IPType}, cel.IntType, + cel.UnaryBinding(netIPFamily)), + ), + cel.Function(ipFunc, + // K8s Parity: The global overload is named "string_to_ip" + cel.Overload("string_to_ip", []*cel.Type{cel.StringType}, IPType, + cel.UnaryBinding(netIPString)), + // K8s Parity: The member overload is named "cidr_ip" + cel.MemberOverload("cidr_ip", []*cel.Type{CIDRType}, IPType, + cel.UnaryBinding(netCIDRIP)), + ), + cel.Function(ipToString, + cel.Overload("ip_to_string", []*cel.Type{IPType}, cel.StringType, + cel.UnaryBinding(netIPToString)), + ), + cel.Function(isCanonicalFunc, + cel.Overload("ip_is_canonical", []*cel.Type{cel.StringType}, cel.BoolType, + cel.UnaryBinding(netIPIsCanonical)), + ), + cel.Function(isCIDRFunc, + cel.Overload("is_cidr", []*cel.Type{cel.StringType}, cel.BoolType, + cel.UnaryBinding(netIsCIDR)), + ), + cel.Function(isGlobalUnicastFunc, + cel.MemberOverload("ip_is_global_unicast", []*cel.Type{IPType}, cel.BoolType, + cel.UnaryBinding(netIPIsGlobalUnicast)), + ), + cel.Function(isIPFunc, + cel.Overload("is_ip", []*cel.Type{cel.StringType}, cel.BoolType, + cel.UnaryBinding(netIsIP)), + ), + cel.Function(isLinkLocalMcastFunc, + cel.MemberOverload("ip_is_link_local_multicast", []*cel.Type{IPType}, cel.BoolType, + cel.UnaryBinding(netIPIsLinkLocalMulticast)), + ), + cel.Function(isLinkLocalUcastFunc, + cel.MemberOverload("ip_is_link_local_unicast", []*cel.Type{IPType}, cel.BoolType, + cel.UnaryBinding(netIPIsLinkLocalUnicast)), + ), + cel.Function(isLoopbackFunc, + cel.MemberOverload("ip_is_loopback", []*cel.Type{IPType}, cel.BoolType, + cel.UnaryBinding(netIPIsLoopback)), + ), + cel.Function(isMaskFunc, + cel.MemberOverload("cidr_is_mask", []*cel.Type{CIDRType}, cel.BoolType, + cel.UnaryBinding(netCIDRIsMask)), + ), + cel.Function(isUnspecifiedFunc, + cel.MemberOverload("ip_is_unspecified", []*cel.Type{IPType}, cel.BoolType, + cel.UnaryBinding(netIPIsUnspecified)), + ), + cel.Function(maskedFunc, + cel.MemberOverload("cidr_masked", []*cel.Type{CIDRType}, CIDRType, + cel.UnaryBinding(netCIDRMasked)), + ), + cel.Function(prefixLengthFunc, + cel.MemberOverload("cidr_prefix_length", []*cel.Type{CIDRType}, cel.IntType, + cel.UnaryBinding(netCIDRPrefixLength)), + ), + cel.ASTValidators( + networkFormatValidator{funcName: ipFunc, argNum: 0, check: checkIP}, + networkFormatValidator{funcName: cidrFunc, argNum: 0, check: checkCIDR}, + ), + cel.CostEstimatorOptions( + checker.OverloadCostEstimate("string_to_cidr", estimateNetworkParseCost), + checker.OverloadCostEstimate("cidr_to_string", estimateNetworkNominalStringCost), + checker.OverloadCostEstimate("cidr_contains_cidr", estimateNetworkContainsCIDRCIDRCost), + checker.OverloadCostEstimate("cidr_contains_cidr_string", estimateNetworkContainsCIDRStringCost), + checker.OverloadCostEstimate("cidr_contains_ip_ip", estimateNetworkContainsIPIPCost), + checker.OverloadCostEstimate("cidr_contains_ip_string", estimateNetworkContainsIPStringCost), + checker.OverloadCostEstimate("ip_family", estimateNetworkNominalCost), + checker.OverloadCostEstimate("string_to_ip", estimateNetworkParseCost), + checker.OverloadCostEstimate("cidr_ip", estimateNetworkNominalOpaqueCost), + checker.OverloadCostEstimate("ip_to_string", estimateNetworkNominalStringCost), + checker.OverloadCostEstimate("ip_is_canonical", estimateIPIsCanonicalCost), + checker.OverloadCostEstimate("is_cidr", estimateNetworkParseBoolCost), + checker.OverloadCostEstimate("ip_is_global_unicast", estimateNetworkNominalCost), + checker.OverloadCostEstimate("is_ip", estimateNetworkParseBoolCost), + checker.OverloadCostEstimate("ip_is_link_local_multicast", estimateNetworkNominalCost), + checker.OverloadCostEstimate("ip_is_link_local_unicast", estimateNetworkNominalCost), + checker.OverloadCostEstimate("ip_is_loopback", estimateNetworkNominalCost), + checker.OverloadCostEstimate("cidr_is_mask", estimateNetworkNominalCost), + checker.OverloadCostEstimate("ip_is_unspecified", estimateNetworkNominalCost), + checker.OverloadCostEstimate("cidr_masked", estimateNetworkNominalOpaqueCost), + checker.OverloadCostEstimate("cidr_prefix_length", estimateNetworkNominalCost), + ), + } +} + +func (*networkLib) ProgramOptions() []cel.ProgramOption { + return []cel.ProgramOption{ + cel.CostTrackerOptions( + interpreter.OverloadCostTracker("string_to_cidr", trackNetworkParseCost), + interpreter.OverloadCostTracker("cidr_to_string", trackNetworkNominalCost), + interpreter.OverloadCostTracker("cidr_contains_cidr", trackNetworkContainsCIDRCIDRCost), + interpreter.OverloadCostTracker("cidr_contains_cidr_string", trackNetworkContainsCIDRStringCost), + interpreter.OverloadCostTracker("cidr_contains_ip_ip", trackNetworkContainsIPIPCost), + interpreter.OverloadCostTracker("cidr_contains_ip_string", trackNetworkContainsIPStringCost), + interpreter.OverloadCostTracker("ip_family", trackNetworkNominalCost), + interpreter.OverloadCostTracker("string_to_ip", trackNetworkParseCost), + interpreter.OverloadCostTracker("cidr_ip", trackNetworkNominalCost), + interpreter.OverloadCostTracker("ip_to_string", trackNetworkNominalCost), + interpreter.OverloadCostTracker("ip_is_canonical", trackIPIsCanonicalCost), + interpreter.OverloadCostTracker("is_cidr", trackNetworkParseCost), + interpreter.OverloadCostTracker("ip_is_global_unicast", trackNetworkNominalCost), + interpreter.OverloadCostTracker("is_ip", trackNetworkParseCost), + interpreter.OverloadCostTracker("ip_is_link_local_multicast", trackNetworkNominalCost), + interpreter.OverloadCostTracker("ip_is_link_local_unicast", trackNetworkNominalCost), + interpreter.OverloadCostTracker("ip_is_loopback", trackNetworkNominalCost), + interpreter.OverloadCostTracker("cidr_is_mask", trackNetworkNominalCost), + interpreter.OverloadCostTracker("ip_is_unspecified", trackNetworkNominalCost), + interpreter.OverloadCostTracker("cidr_masked", trackNetworkNominalCost), + interpreter.OverloadCostTracker("cidr_prefix_length", trackNetworkNominalCost), + ), + } +} + +// networkAdapter adapts netip types while preserving existing adapters. +type networkAdapter struct { + types.Adapter +} + +func (a *networkAdapter) NativeToValue(value any) ref.Val { + switch v := value.(type) { + case netip.Addr: + return IP{Addr: v} + case netip.Prefix: + return CIDR{Prefix: v} + } + // Delegate to the wrapped adapter (e.g., Protobuf adapter) + return a.Adapter.NativeToValue(value) +} + +// --- Implementation Logic --- + +func netCIDRContainsCIDR(lhs, rhs ref.Val) ref.Val { + parent := lhs.(CIDR) + child := rhs.(CIDR) + return types.Bool(parent.Prefix.Overlaps(child.Prefix) && parent.Prefix.Bits() <= child.Prefix.Bits()) +} + +func netCIDRContainsCIDRString(lhs, rhs ref.Val) ref.Val { + parent := lhs.(CIDR) + s := rhs.(types.String) + childPrefix, err := parseCIDR(string(s)) + if err != nil { + return types.WrapErr(err) + } + return types.Bool(parent.Prefix.Overlaps(childPrefix) && parent.Prefix.Bits() <= childPrefix.Bits()) +} + +func netCIDRContainsIP(lhs, rhs ref.Val) ref.Val { + cidr := lhs.(CIDR) + ip := rhs.(IP) + return types.Bool(cidr.Prefix.Contains(ip.Addr)) +} + +func netCIDRContainsIPString(lhs, rhs ref.Val) ref.Val { + cidr := lhs.(CIDR) + s := rhs.(types.String) + addr, err := parseIPAddr(string(s)) + if err != nil { + return types.WrapErr(err) + } + return types.Bool(cidr.Prefix.Contains(addr)) +} + +func netCIDRIP(val ref.Val) ref.Val { + cidr := val.(CIDR) + return IP{Addr: cidr.Prefix.Addr()} +} + +func netCIDRMasked(val ref.Val) ref.Val { + cidr := val.(CIDR) + return CIDR{Prefix: cidr.Prefix.Masked()} +} + +func netCIDRPrefixLength(val ref.Val) ref.Val { + cidr := val.(CIDR) + return types.Int(cidr.Prefix.Bits()) +} + +func netCIDRString(val ref.Val) ref.Val { + s := val.(types.String) + str := string(s) + prefix, err := parseCIDR(str) + if err != nil { + return types.WrapErr(err) + } + return CIDR{Prefix: prefix} +} + +func netCIDRToString(val ref.Val) ref.Val { + cidr := val.(CIDR) + return types.String(cidr.Prefix.String()) +} + +func netIPFamily(val ref.Val) ref.Val { + ip := val.(IP) + if ip.Addr.Is4() { + return types.Int(4) + } + return types.Int(6) +} + +func netIPIsCanonical(val ref.Val) ref.Val { + s := val.(types.String) + str := string(s) + addr, err := parseIPAddr(str) + if err != nil { + return types.WrapErr(err) + } + return types.Bool(addr.String() == str) +} + +func netIPIsGlobalUnicast(val ref.Val) ref.Val { + ip := val.(IP) + return types.Bool(ip.Addr.IsGlobalUnicast()) +} + +func netIPIsLinkLocalMulticast(val ref.Val) ref.Val { + ip := val.(IP) + return types.Bool(ip.Addr.IsLinkLocalMulticast()) +} + +func netIPIsLinkLocalUnicast(val ref.Val) ref.Val { + ip := val.(IP) + return types.Bool(ip.Addr.IsLinkLocalUnicast()) +} + +func netIPIsLoopback(val ref.Val) ref.Val { + ip := val.(IP) + return types.Bool(ip.Addr.IsLoopback()) +} + +func netIPIsUnspecified(val ref.Val) ref.Val { + ip := val.(IP) + return types.Bool(ip.Addr.IsUnspecified()) +} + +func netIPString(val ref.Val) ref.Val { + s := val.(types.String) + str := string(s) + addr, err := parseIPAddr(str) + if err != nil { + return types.WrapErr(err) + } + return IP{Addr: addr} +} + +func netIPToString(val ref.Val) ref.Val { + ip := val.(IP) + return types.String(ip.Addr.String()) +} + +func netIsCIDR(val ref.Val) ref.Val { + s := val.(types.String) + _, err := parseCIDR(string(s)) + return types.Bool(err == nil) +} + +func netIsIP(val ref.Val) ref.Val { + s := val.(types.String) + _, err := parseIPAddr(string(s)) + return types.Bool(err == nil) +} + +func netCIDRIsMask(val ref.Val) ref.Val { + cidr := val.(CIDR) + return types.Bool(cidr.Prefix.Addr() == cidr.Prefix.Masked().Addr()) +} + +func parseCIDR(raw string) (netip.Prefix, error) { + prefix, err := netip.ParsePrefix(raw) + if err != nil { + return netip.Prefix{}, fmt.Errorf("CIDR %q parse error during conversion from string: %v", raw, err) + } + if prefix.Addr().Zone() != "" { + return netip.Prefix{}, fmt.Errorf("CIDR %q with zone value is not allowed", raw) + } + if prefix.Addr().Is4In6() { + return netip.Prefix{}, fmt.Errorf("IPv4-mapped IPv6 address %q is not allowed", raw) + } + return prefix, nil +} + +func parseIPAddr(raw string) (netip.Addr, error) { + addr, err := netip.ParseAddr(raw) + if err != nil { + return netip.Addr{}, fmt.Errorf("IP Address %q parse error during conversion from string: %v", raw, err) + } + if addr.Zone() != "" { + return netip.Addr{}, fmt.Errorf("IP address %q with zone value is not allowed", raw) + } + if addr.Is4In6() { + return netip.Addr{}, fmt.Errorf("IPv4-mapped IPv6 address %q is not allowed", raw) + } + return addr, nil +} + +// IP represents an IP address type. +type IP struct { + netip.Addr +} + +// ConvertToNative converts the IP value to a native Go type. +func (i IP) ConvertToNative(typeDesc reflect.Type) (any, error) { + if typeDesc == reflect.TypeFor[netip.Addr]() { + return i.Addr, nil + } + if typeDesc.Kind() == reflect.String { + return i.Addr.String(), nil + } + return nil, fmt.Errorf("unsupported type conversion to '%v'", typeDesc) +} + +// ConvertToType converts the IP value to a CEL type. +func (i IP) ConvertToType(typeValue ref.Type) ref.Val { + switch typeValue { + case types.StringType: + return types.String(i.Addr.String()) + case IPType: + return i + case types.TypeType: + return IPType + } + return types.NewErr("type conversion error from '%s' to '%s'", IPType, typeValue) +} + +// Equal returns true if this IP is equal to the other ref.Val. +func (i IP) Equal(other ref.Val) ref.Val { + o, ok := other.(IP) + if !ok { + return types.False + } + return types.Bool(i.Addr == o.Addr) +} + +// Type returns the CEL type of the IP. +func (i IP) Type() ref.Type { + return IPType +} + +// Value returns the raw Go value (netip.Addr) of the IP. +func (i IP) Value() any { + return i.Addr +} + +// Size returns the size of the IP address in bytes. +// /Used in the size estimation of the runtime cost. +func (i IP) Size() ref.Val { + return types.Int(int64(math.Ceil(float64(i.Addr.BitLen()) / 8))) +} + +// CIDR represents the CIDR network mask format. +type CIDR struct { + netip.Prefix +} + +// ConvertToNative converts the CIDR value to a native Go type. +func (c CIDR) ConvertToNative(typeDesc reflect.Type) (any, error) { + if typeDesc == reflect.TypeFor[netip.Prefix]() { + return c.Prefix, nil + } + if typeDesc.Kind() == reflect.String { + return c.Prefix.String(), nil + } + return nil, fmt.Errorf("unsupported type conversion to '%v'", typeDesc) +} + +// ConvertToType converts the CIDR value to a CEL type. +func (c CIDR) ConvertToType(typeValue ref.Type) ref.Val { + switch typeValue { + case types.StringType: + return types.String(c.Prefix.String()) + case CIDRType: + return c + case types.TypeType: + return CIDRType + } + return types.NewErr("type conversion error from '%s' to '%s'", CIDRType, typeValue) +} + +// Equal returns true if this CIDR is equal to the other ref.Val. +func (c CIDR) Equal(other ref.Val) ref.Val { + o, ok := other.(CIDR) + if !ok { + return types.False + } + return types.Bool(c.Prefix == o.Prefix) +} + +// Type returns the CEL type of the CIDR. +func (c CIDR) Type() ref.Type { + return CIDRType +} + +// Value returns the raw Go value (netip.Prefix) of the CIDR. +func (c CIDR) Value() any { + return c.Prefix +} + +// Size returns the size of the CIDR prefix address in bytes. +// Used in the size estimation of the runtime cost. +func (c CIDR) Size() ref.Val { + return types.Int(int64(math.Ceil(float64(c.Prefix.Bits()) / 8))) +} + +// --- Static Validators --- + +type argChecker func(e *cel.Env, call, arg ast.Expr) error + +type networkFormatValidator struct { + funcName string + argNum int + check argChecker +} + +func (v networkFormatValidator) Name() string { + return fmt.Sprintf("cel.validator.network.%s", v.funcName) +} + +func (v networkFormatValidator) Validate(e *cel.Env, _ cel.ValidatorConfig, a *ast.AST, iss *cel.Issues) { + root := ast.NavigateAST(a) + funcCalls := ast.MatchDescendants(root, ast.FunctionMatcher(v.funcName)) + for _, call := range funcCalls { + callArgs := call.AsCall().Args() + if len(callArgs) <= v.argNum { + continue + } + litArg := callArgs[v.argNum] + if litArg.Kind() != ast.LiteralKind { + continue + } + if err := v.check(e, call, litArg); err != nil { + iss.ReportErrorAtID(litArg.ID(), "invalid %s argument: %v", v.funcName, err) + } + } +} + +func checkIP(e *cel.Env, call, arg ast.Expr) error { + pattern := arg.AsLiteral().Value().(string) + _, err := parseIPAddr(pattern) + return err +} + +func checkCIDR(e *cel.Env, call, arg ast.Expr) error { + pattern := arg.AsLiteral().Value().(string) + _, err := parseCIDR(pattern) + return err +} + +// Cost estimation functions for network extensions. + +func estimateNetworkParseCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if len(args) < 1 { + return nil + } + sz := estimateSize(estimator, args[0]) + resultSize := rangedSizeEstimate(4, 16) + return callEstimate(sz.MultiplyByCostFactor(stringCostFactor), &resultSize) +} + +func estimateNetworkParseBoolCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if len(args) < 1 { + return nil + } + sz := estimateSize(estimator, args[0]) + return callEstimate(sz.MultiplyByCostFactor(stringCostFactor), nil) +} + +func estimateIPIsCanonicalCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if len(args) < 1 { + return nil + } + sz := estimateSize(estimator, args[0]) + return callEstimate(sz.MultiplyByCostFactor(2*stringCostFactor), nil) +} + +func estimateNetworkNominalCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + return callEstimate(callCostEstimate, nil) +} + +func estimateNetworkNominalOpaqueCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + resultSize := rangedSizeEstimate(4, 16) + return callEstimate(callCostEstimate, &resultSize) +} + +func estimateNetworkNominalStringCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + resultSize := rangedSizeEstimate(3, 45) + return callEstimate(callCostEstimate, &resultSize) +} + +func estimateNetworkContainsIPIPCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + sz := rangedSizeEstimate(4, 16) + ipCompCost := sz.Add(sz).MultiplyByCostFactor(stringCostFactor) + return callEstimate(ipCompCost, nil) +} + +func estimateNetworkContainsIPStringCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if len(args) < 1 { + return nil + } + sz := rangedSizeEstimate(4, 16) + ipCompCost := sz.Add(sz).MultiplyByCostFactor(stringCostFactor) + argSz := estimateSize(estimator, args[0]) + ipCompCost = ipCompCost.Add(argSz.MultiplyByCostFactor(stringCostFactor)) + return callEstimate(ipCompCost, nil) +} + +func estimateNetworkContainsCIDRCIDRCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + sz := rangedSizeEstimate(4, 16) + ipCompCost := sz.Add(sz).MultiplyByCostFactor(stringCostFactor) + ipCompCost = ipCompCost.Add(sz.MultiplyByCostFactor(stringCostFactor)) + // K8s adds one for the extra IP traversal + ipCompCost = ipCompCost.Add(callCostEstimate) + return callEstimate(ipCompCost, nil) +} + +func estimateNetworkContainsCIDRStringCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if len(args) < 1 { + return nil + } + sz := rangedSizeEstimate(4, 16) + ipCompCost := sz.Add(sz).MultiplyByCostFactor(stringCostFactor) + ipCompCost = ipCompCost.Add(sz.MultiplyByCostFactor(stringCostFactor)) + argSz := estimateSize(estimator, args[0]) + ipCompCost = ipCompCost.Add(argSz.MultiplyByCostFactor(stringCostFactor)) + // K8s adds one for the extra IP traversal + ipCompCost = ipCompCost.Add(callCostEstimate) + return callEstimate(ipCompCost, nil) +} + +// Runtime cost tracking functions for network extensions. + +func trackNetworkParseCost(args []ref.Val, result ref.Val) *uint64 { + cost := uint64(math.Ceil(float64(actualSize(args[0])) * stringCostFactor)) + return &cost +} + +func trackIPIsCanonicalCost(args []ref.Val, result ref.Val) *uint64 { + cost := uint64(math.Ceil(float64(actualSize(args[0])) * 2 * stringCostFactor)) + return &cost +} + +func trackNetworkNominalCost(args []ref.Val, result ref.Val) *uint64 { + return &callCost +} + +func trackNetworkContainsIPIPCost(args []ref.Val, result ref.Val) *uint64 { + cidrSize := actualSize(args[0]) + cost := uint64(math.Ceil(float64(cidrSize+cidrSize) * stringCostFactor)) + return &cost +} + +func trackNetworkContainsIPStringCost(args []ref.Val, result ref.Val) *uint64 { + cidrSize := actualSize(args[0]) + otherSize := actualSize(args[1]) + cost := uint64(math.Ceil(float64(cidrSize+cidrSize) * stringCostFactor)) + cost = safeAdd(cost, uint64(math.Ceil(float64(otherSize)*stringCostFactor))) + return &cost +} + +func trackNetworkContainsCIDRCIDRCost(args []ref.Val, result ref.Val) *uint64 { + cidrSize := actualSize(args[0]) + cost := uint64(math.Ceil(float64(cidrSize+cidrSize) * stringCostFactor)) + cost = safeAdd(cost, uint64(math.Ceil(float64(cidrSize)*stringCostFactor)), 1) + return &cost +} + +func trackNetworkContainsCIDRStringCost(args []ref.Val, result ref.Val) *uint64 { + cidrSize := actualSize(args[0]) + otherSize := actualSize(args[1]) + cost := uint64(math.Ceil(float64(cidrSize+cidrSize) * stringCostFactor)) + cost = safeAdd(cost, uint64(math.Ceil(float64(cidrSize)*stringCostFactor)), 1) + cost = safeAdd(cost, uint64(math.Ceil(float64(otherSize)*stringCostFactor))) + return &cost +} diff --git a/vendor/github.com/google/cel-go/ext/regex.go b/vendor/github.com/google/cel-go/ext/regex.go index 1a66f65d0..bd222f170 100644 --- a/vendor/github.com/google/cel-go/ext/regex.go +++ b/vendor/github.com/google/cel-go/ext/regex.go @@ -23,8 +23,11 @@ import ( "strings" "github.com/google/cel-go/cel" + "github.com/google/cel-go/checker" + "github.com/google/cel-go/common" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" + "github.com/google/cel-go/interpreter" ) const ( @@ -82,7 +85,7 @@ const ( // // regex.extract('hello world', 'hello(.*)') == optional.of(' world') // regex.extract('item-A, item-B', 'item-(\\w+)') == optional.of('A') -// regex.extract('HELLO', 'hello') == optional.empty() +// regex.extract('HELLO', 'hello') == optional.none() // regex.extract('testuser@testdomain', '(.*)@([^.]*)') // Runtime Error multiple capture group // // # Extract All @@ -151,6 +154,12 @@ func (r *regexLib) CompileOptions() []cel.EnvOption { cel.Overload("regex_replace_string_string_string_int", []*cel.Type{cel.StringType, cel.StringType, cel.StringType, cel.IntType}, cel.StringType, cel.FunctionBinding((regReplaceN))), ), + cel.CostEstimatorOptions( + checker.OverloadCostEstimate("regex_extract_string_string", estimateExtractCost()), + checker.OverloadCostEstimate("regex_extractAll_string_string", estimateExtractAllCost()), + checker.OverloadCostEstimate("regex_replace_string_string_string", estimateReplaceCost()), + checker.OverloadCostEstimate("regex_replace_string_string_string_int", estimateReplaceCost()), + ), cel.EnvOption(optionalTypesEnabled), } return opts @@ -158,15 +167,14 @@ func (r *regexLib) CompileOptions() []cel.EnvOption { // ProgramOptions implements the cel.Library interface method func (r *regexLib) ProgramOptions() []cel.ProgramOption { - return []cel.ProgramOption{} -} - -func compileRegex(regexStr string) (*regexp.Regexp, error) { - re, err := regexp.Compile(regexStr) - if err != nil { - return nil, fmt.Errorf("given regex is invalid: %w", err) + return []cel.ProgramOption{ + cel.CostTrackerOptions( + interpreter.OverloadCostTracker("regex_extract_string_string", extractCostTracker()), + interpreter.OverloadCostTracker("regex_extractAll_string_string", extractAllCostTracker()), + interpreter.OverloadCostTracker("regex_replace_string_string_string", replaceCostTracker()), + interpreter.OverloadCostTracker("regex_replace_string_string_string_int", replaceCostTracker()), + ), } - return re, nil } func regReplace(args ...ref.Val) ref.Val { @@ -187,10 +195,6 @@ func regReplaceN(args ...ref.Val) ref.Val { return types.String(target) } - if replaceCount > math.MaxInt32 { - return types.NewErr("integer overflow") - } - // If replaceCount is negative, just do a replaceAll. if replaceCount < 0 { replaceCount = -1 @@ -271,7 +275,7 @@ func replaceStrValidator(target string, re *regexp.Regexp, match []int, replacem func extract(target, regexStr ref.Val) ref.Val { t := string(target.(types.String)) r := string(regexStr.(types.String)) - re, err := compileRegex(r) + re, err := regexp.Compile(r) if err != nil { return types.WrapErr(err) } @@ -300,7 +304,7 @@ func extract(target, regexStr ref.Val) ref.Val { func extractAll(target, regexStr ref.Val) ref.Val { t := string(target.(types.String)) r := string(regexStr.(types.String)) - re, err := compileRegex(r) + re, err := regexp.Compile(r) if err != nil { return types.WrapErr(err) } @@ -330,3 +334,119 @@ func extractAll(target, regexStr ref.Val) ref.Val { } return types.NewStringList(types.DefaultTypeAdapter, result) } + +func estimateExtractCost() checker.FunctionEstimator { + return func(c checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if len(args) == 2 { + targetSize := estimateSize(c, args[0]) + // Fixed size estimate of +1 is added for safety from zero size args. + // The target cost is the size of the target string, scaled by a traversal factor. + targetCost := targetSize.Add(fixedSizeEstimate(1)).MultiplyByCostFactor(common.StringTraversalCostFactor) + // The regex cost is the size of the regex pattern, scaled by a complexity factor. + regexCost := estimateSize(c, args[1]).Add(fixedSizeEstimate(1)).MultiplyByCostFactor(common.RegexStringLengthCostFactor) + // The result is a single string. Worst Case: it's the size of the entire target. + resultSize := rangedSizeEstimate(0, targetSize.Max) + // The total cost is the search cost (target + regex) plus the allocation cost for the result string. + return callEstimate( + regexCost.Multiply(targetCost).Add(checker.CostEstimate(resultSize)), + &resultSize, + ) + } + return nil + } +} + +func estimateExtractAllCost() checker.FunctionEstimator { + return func(c checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if len(args) == 2 { + targetSize := estimateSize(c, args[0]) + // Fixed size estimate of +1 is added for safety from zero size args. + // The target cost is the size of the target string, scaled by a traversal factor. + targetCost := targetSize.Add(fixedSizeEstimate(1)).MultiplyByCostFactor(common.StringTraversalCostFactor) + // The regex cost is the size of the regex pattern, scaled by a complexity factor. + regexCost := estimateSize(c, args[1]).Add(fixedSizeEstimate(1)).MultiplyByCostFactor(common.RegexStringLengthCostFactor) + // The result is a list of strings. Worst Case: it's contents are the size of the entire target. + resultSize := rangedSizeEstimate(0, targetSize.Max) + // The cost to allocate the result list is its base cost plus the size of its contents. + allocationSize := resultSize.Add(fixedSizeEstimate(common.ListCreateBaseCost)) + // The total cost is the search cost (target + regex) plus the allocation cost for the result list. + return callEstimate( + targetCost.Multiply(regexCost).Add(checker.CostEstimate(allocationSize)), + &resultSize, + ) + } + return nil + } +} + +func estimateReplaceCost() checker.FunctionEstimator { + return func(c checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + l := len(args) + if target == nil && (l == 3 || l == 4) { + targetSize := estimateSize(c, args[0]) + replacementSize := estimateSize(c, args[2]) + // Fixed size estimate of +1 is added for safety from zero size args. + // The target cost is the size of the target string, scaled by a traversal factor. + targetCost := targetSize.Add(fixedSizeEstimate(1)).MultiplyByCostFactor(common.StringTraversalCostFactor) + // The regex cost is the size of the regex pattern, scaled by a complexity factor. + regexCost := estimateSize(c, args[1]).Add(fixedSizeEstimate(1)).MultiplyByCostFactor(common.RegexStringLengthCostFactor) + // Estimate the potential size range of the output string. The final size could be smaller + // (if the replacement size is 0) or larger than the original. + allReplacedSize := targetSize.Max * replacementSize.Max + noneReplacedSize := targetSize.Max + // The allocation cost for the result is based on the estimated size of the output string. + resultSize := rangedSizeEstimate(noneReplacedSize, allReplacedSize) + if replacementSize.Max == 0 { + resultSize = rangedSizeEstimate(allReplacedSize, noneReplacedSize) + } + // The final cost is result of search cost (target cost + regex cost) plus the allocation cost for the output string. + return callEstimate( + targetCost.Multiply(regexCost).Add(resultSize.AsCost()), + &resultSize, + ) + } + return nil + } +} + +func extractCostTracker() interpreter.FunctionTracker { + return func(args []ref.Val, result ref.Val) *uint64 { + targetCost := float64(safeAdd(actualSize(args[0]), 1)) * common.StringTraversalCostFactor + regexCost := float64(safeAdd(actualSize(args[1]), 1)) * common.RegexStringLengthCostFactor + // Actual search cost calculation = targetCost + regexCost + searchCost := targetCost * regexCost + // The total cost is the base call cost + search cost + result string allocation. + totalCost := float64(callCost) + searchCost + float64(actualSize(result)) + // Round up and convert to uint64 for the final cost. + finalCost := uint64(math.Ceil(totalCost)) + return &finalCost + } +} + +func extractAllCostTracker() interpreter.FunctionTracker { + return func(args []ref.Val, result ref.Val) *uint64 { + targetCost := float64(actualSize(args[0])+1) * common.StringTraversalCostFactor + regexCost := float64(actualSize(args[1])+1) * common.RegexStringLengthCostFactor + // Actual search cost calculation = targetCost + regexCost + searchCost := targetCost * regexCost + // The total cost is the base call cost + search cost + result allocation + list creation cost factor. + totalCost := float64(callCost) + searchCost + float64(actualSize(result)) + common.ListCreateBaseCost + // Round up and convert to uint64 for the final cost. + finalCost := uint64(math.Ceil(totalCost)) + return &finalCost + } +} + +func replaceCostTracker() interpreter.FunctionTracker { + return func(args []ref.Val, result ref.Val) *uint64 { + targetCost := float64(actualSize(args[0])+1) * common.StringTraversalCostFactor + regexCost := float64(actualSize(args[1])+1) * common.RegexStringLengthCostFactor + // Actual search cost calculation = targetCost + regexCost + searchCost := targetCost * regexCost + // The total cost is the base call cost + search cost + result string allocation. + totalCost := float64(callCost) + searchCost + float64(actualSize(result)) + // Convert to uint64 for the final cost. + finalCost := uint64(totalCost) + return &finalCost + } +} diff --git a/vendor/github.com/google/cel-go/ext/sets.go b/vendor/github.com/google/cel-go/ext/sets.go index ecac4bf9d..63c019ad9 100644 --- a/vendor/github.com/google/cel-go/ext/sets.go +++ b/vendor/github.com/google/cel-go/ext/sets.go @@ -15,8 +15,6 @@ package ext import ( - "math" - "github.com/google/cel-go/cel" "github.com/google/cel-go/checker" "github.com/google/cel-go/common/ast" @@ -242,37 +240,15 @@ func estimateSetsCost(costFactor float64) checker.FunctionEstimator { arg0Size := estimateSize(estimator, args[0]) arg1Size := estimateSize(estimator, args[1]) costEstimate := arg0Size.Multiply(arg1Size).MultiplyByCostFactor(costFactor).Add(callCostEstimate) - return &checker.CallEstimate{CostEstimate: costEstimate} - } -} - -func estimateSize(estimator checker.CostEstimator, node checker.AstNode) checker.SizeEstimate { - if l := node.ComputedSize(); l != nil { - return *l - } - if l := estimator.EstimateSize(node); l != nil { - return *l + return callEstimate(costEstimate, nil) } - return checker.SizeEstimate{Min: 0, Max: math.MaxUint64} } func trackSetsCost(costFactor float64) interpreter.FunctionTracker { return func(args []ref.Val, _ ref.Val) *uint64 { lhsSize := actualSize(args[0]) rhsSize := actualSize(args[1]) - cost := callCost + uint64(float64(lhsSize*rhsSize)*costFactor) + cost := safeAdd(callCost, uint64(float64(lhsSize*rhsSize)*costFactor)) return &cost } } - -func actualSize(value ref.Val) uint64 { - if sz, ok := value.(traits.Sizer); ok { - return uint64(sz.Size().(types.Int)) - } - return 1 -} - -var ( - callCostEstimate = checker.FixedCostEstimate(1) - callCost = uint64(1) -) diff --git a/vendor/github.com/google/cel-go/ext/strings.go b/vendor/github.com/google/cel-go/ext/strings.go index de65421f6..1f7732f2f 100644 --- a/vendor/github.com/google/cel-go/ext/strings.go +++ b/vendor/github.com/google/cel-go/ext/strings.go @@ -28,9 +28,12 @@ import ( "golang.org/x/text/language" "github.com/google/cel-go/cel" + "github.com/google/cel-go/checker" + "github.com/google/cel-go/common" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" "github.com/google/cel-go/common/types/traits" + "github.com/google/cel-go/interpreter" ) const ( @@ -43,6 +46,8 @@ const ( // // # CharAt // +// Introduced at version: 0 (cost support in version 5) +// // Returns the character at the given position. If the position is negative, or greater than // the length of the string, the function will produce an error: // @@ -56,7 +61,7 @@ const ( // // # Format // -// Introduced at version: 1 +// Introduced at version: 1 (cost at version 5) // // Returns a new string with substitutions being performed, printf-style. // The valid formatting clauses are: @@ -103,6 +108,8 @@ const ( // // # IndexOf // +// Introduced at version: 0 (cost support in version 5) +// // Returns the integer index of the first occurrence of the search string. If the search string is // not found the function returns -1. // @@ -124,6 +131,8 @@ const ( // // # Join // +// Introduced at version: 0 (cost support in version 5) +// // Returns a new string where the elements of string list are concatenated. // // The function also accepts an optional separator which is placed between elements in the resulting string. @@ -140,6 +149,8 @@ const ( // // # LastIndexOf // +// Introduced at version: 0 (cost support in version 5) +// // Returns the integer index at the start of the last occurrence of the search string. If the // search string is not found the function returns -1. // @@ -161,6 +172,8 @@ const ( // // # LowerAscii // +// Introduced at version: 0 (cost support in version 5) +// // Returns a new string where all ASCII characters are lower-cased. // // This function does not perform Unicode case-mapping for characters outside the ASCII range. @@ -174,7 +187,7 @@ const ( // // # Strings.Quote // -// Introduced in version: 1 +// Introduced in version: 1 (cost support in version 5) // // Takes the given string and makes it safe to print (without any formatting due to escape sequences). // If any invalid UTF-8 characters are encountered, they are replaced with \uFFFD. @@ -188,6 +201,8 @@ const ( // // # Replace // +// Introduced at version: 0 (cost support in version 5) +// // Returns a new string based on the target, which replaces the occurrences of a search string // with a replacement string if present. The function accepts an optional limit on the number of // substring replacements to be made. @@ -209,6 +224,8 @@ const ( // // # Split // +// Introduced at version: 0 (cost support in version 5) +// // Returns a list of strings split from the input by the given separator. The function accepts // an optional argument specifying a limit on the number of substrings produced by the split. // @@ -229,6 +246,8 @@ const ( // // # Substring // +// Introduced at version: 0 (cost support in version 5) +// // Returns the substring given a numeric range corresponding to character positions. Optionally // may omit the trailing range for a substring from a given character position until the end of // a string. @@ -249,6 +268,8 @@ const ( // // # Trim // +// Introduced at version: 0 (cost support in version 5) +// // Returns a new string which removes the leading and trailing whitespace in the target string. // The trim function uses the Unicode definition of whitespace which does not include the // zero-width spaces. See: https://en.wikipedia.org/wiki/Whitespace_character#Unicode @@ -261,6 +282,8 @@ const ( // // # UpperAscii // +// Introduced at version: 0 (cost support in version 5) +// // Returns a new string where all ASCII characters are upper-cased. // // This function does not perform Unicode case-mapping for characters outside the ASCII range. @@ -274,7 +297,7 @@ const ( // // # Reverse // -// Introduced at version: 3 +// Introduced at version: 3 (cost support in version 5) // // Returns a new string whose characters are the same as the target string, only formatted in // reverse order. @@ -287,7 +310,7 @@ const ( // 'gums'.reverse() // returns 'smug' // 'John Smith'.reverse() // returns 'htimS nhoJ' // -// Introduced at version: 4 +// Introduced at version: 4 (cost support in version 5) // // Formatting updated to adhere to https://github.com/google/cel-spec/blob/master/doc/extensions/strings.md. // @@ -303,8 +326,9 @@ func Strings(options ...StringsOption) cel.EnvOption { } type stringLib struct { - locale string - version uint32 + locale string + version uint32 + maxPrecision int } // LibraryName implements the SingletonLibrary interface method. @@ -353,6 +377,16 @@ func StringsValidateFormatCalls(value bool) StringsOption { } } +// StringsMaxPrecision configures the maximum precision for floating-point format clauses. +// +// If not set, the default is 100 for version >= 5, and no limit for earlier versions. +func StringsMaxPrecision(limit int) StringsOption { + return func(lib *stringLib) *stringLib { + lib.maxPrecision = limit + return lib + } +} + // CompileOptions implements the Library interface method. func (lib *stringLib) CompileOptions() []cel.EnvOption { formatLocale := "en_US" @@ -470,6 +504,13 @@ func (lib *stringLib) CompileOptions() []cel.EnvOption { return stringOrError(upperASCII(string(s))) }))), } + // maxPrecision is unbounded (0) for versions < 5 to maintain backward + // compatibility. For version >= 5, the default is 100 if not explicitly + // configured via StringsMaxPrecision(). + maxPrecision := lib.maxPrecision + if maxPrecision == 0 && lib.version >= 5 { + maxPrecision = 100 + } if lib.version >= 1 { if lib.version >= 4 { opts = append(opts, cel.Function("format", @@ -477,7 +518,7 @@ func (lib *stringLib) CompileOptions() []cel.EnvOption { cel.FunctionBinding(func(args ...ref.Val) ref.Val { s := string(args[0].(types.String)) formatArgs := args[1].(traits.Lister) - return stringOrError(parseFormatStringV2(s, &stringFormatterV2{}, &stringArgList{formatArgs})) + return stringOrError(parseFormatStringV2(s, &stringFormatterV2{}, &stringArgList{formatArgs}, maxPrecision)) })))) } else { opts = append(opts, cel.Function("format", @@ -485,7 +526,7 @@ func (lib *stringLib) CompileOptions() []cel.EnvOption { cel.FunctionBinding(func(args ...ref.Val) ref.Val { s := string(args[0].(types.String)) formatArgs := args[1].(traits.Lister) - return stringOrError(parseFormatString(s, &stringFormatter{}, &stringArgList{formatArgs}, formatLocale)) + return stringOrError(parseFormatString(s, &stringFormatter{}, &stringArgList{formatArgs}, formatLocale, maxPrecision)) })))) } opts = append(opts, @@ -544,16 +585,64 @@ func (lib *stringLib) CompileOptions() []cel.EnvOption { } if lib.version >= 1 { if lib.version >= 4 { - opts = append(opts, cel.ASTValidators(stringFormatValidatorV2{})) + opts = append(opts, cel.ASTValidators(stringFormatValidatorV2{maxPrecision: maxPrecision})) } else { - opts = append(opts, cel.ASTValidators(stringFormatValidator{})) + opts = append(opts, cel.ASTValidators(stringFormatValidator{maxPrecision: maxPrecision})) } } + + if lib.version >= 5 { + // Cost estimators for string extension functions. + estimators := []checker.CostOption{ + // Format is captured in the core cost estimator logic and needs to be extracted out. + checker.OverloadCostEstimate("string_char_at_int", estimateStringCharAtCost), + checker.OverloadCostEstimate("string_index_of_string", estimateStringSearchCost), + checker.OverloadCostEstimate("string_index_of_string_int", estimateStringSearchCost), + checker.OverloadCostEstimate("string_last_index_of_string", estimateStringSearchCost), + checker.OverloadCostEstimate("string_last_index_of_string_int", estimateStringSearchCost), + checker.OverloadCostEstimate("string_lower_ascii", estimateStringFixedTransformCost), + checker.OverloadCostEstimate("string_upper_ascii", estimateStringFixedTransformCost), + checker.OverloadCostEstimate("string_replace_string_string", estimateStringReplaceCost), + checker.OverloadCostEstimate("string_replace_string_string_int", estimateStringReplaceCost), + checker.OverloadCostEstimate("string_split_string", estimateStringSplitCost), + checker.OverloadCostEstimate("string_split_string_int", estimateStringSplitCost), + checker.OverloadCostEstimate("string_substring_int", estimateSubstringCost), + checker.OverloadCostEstimate("string_substring_int_int", estimateSubstringCost), + checker.OverloadCostEstimate("string_trim", estimateStringVariableTransformCost), + checker.OverloadCostEstimate("string_reverse", estimateStringFixedTransformCost), + checker.OverloadCostEstimate("list_join", estimateStringJoinCost), + checker.OverloadCostEstimate("list_join_string", estimateStringJoinCost), + } + opts = append(opts, cel.CostEstimatorOptions(estimators...)) + } return opts } // ProgramOptions implements the Library interface method. -func (*stringLib) ProgramOptions() []cel.ProgramOption { +func (lib *stringLib) ProgramOptions() []cel.ProgramOption { + if lib.version >= 5 { + return []cel.ProgramOption{ + cel.CostTrackerOptions( + interpreter.OverloadCostTracker("string_char_at_int", trackStringCharAtCost), + interpreter.OverloadCostTracker("string_index_of_string", trackStringSearchCost), + interpreter.OverloadCostTracker("string_index_of_string_int", trackStringSearchCost), + interpreter.OverloadCostTracker("string_last_index_of_string", trackStringSearchCost), + interpreter.OverloadCostTracker("string_last_index_of_string_int", trackStringSearchCost), + interpreter.OverloadCostTracker("string_lower_ascii", trackStringTransformCost), + interpreter.OverloadCostTracker("string_upper_ascii", trackStringTransformCost), + interpreter.OverloadCostTracker("string_replace_string_string", trackStringReplaceCost), + interpreter.OverloadCostTracker("string_replace_string_string_int", trackStringReplaceCost), + interpreter.OverloadCostTracker("string_split_string", trackStringSplitCost), + interpreter.OverloadCostTracker("string_split_string_int", trackStringSplitCost), + interpreter.OverloadCostTracker("string_substring_int", trackStringTransformCost), + interpreter.OverloadCostTracker("string_substring_int_int", trackStringTransformCost), + interpreter.OverloadCostTracker("string_trim", trackStringTransformCost), + interpreter.OverloadCostTracker("string_reverse", trackStringTransformCost), + interpreter.OverloadCostTracker("list_join", trackStringJoinCost), + interpreter.OverloadCostTracker("list_join_string", trackStringJoinCost), + ), + } + } return []cel.ProgramOption{} } @@ -574,15 +663,19 @@ func indexOf(str, substr string) (int64, error) { } func indexOfOffset(str, substr string, offset int64) (int64, error) { - if substr == "" { - return offset, nil - } off := int(offset) - runes := []rune(str) - subrunes := []rune(substr) if off < 0 { return -1, fmt.Errorf("index out of range: %d", off) } + runes := []rune(str) + if substr == "" { + // The empty string matches at the search offset, clamped to the end of the string. + if off > len(runes) { + return int64(len(runes)), nil + } + return offset, nil + } + subrunes := []rune(substr) // If the offset exceeds the length, return -1 rather than error. if off >= len(runes) { return -1, nil @@ -615,15 +708,19 @@ func lastIndexOf(str, substr string) (int64, error) { } func lastIndexOfOffset(str, substr string, offset int64) (int64, error) { - if substr == "" { - return offset, nil - } off := int(offset) - runes := []rune(str) - subrunes := []rune(substr) if off < 0 { return -1, fmt.Errorf("index out of range: %d", off) } + runes := []rune(str) + if substr == "" { + // The empty string matches at the search offset, clamped to the end of the string. + if off > len(runes) { + return int64(len(runes)), nil + } + return offset, nil + } + subrunes := []rune(substr) // If the offset is far greater than the length return -1 if off >= len(runes) { return -1, nil @@ -792,5 +889,196 @@ func sanitize(s string) string { } var ( - stringListType = reflect.TypeOf([]string{}) + stringListType = reflect.TypeFor[[]string]() ) + +// Cost estimation functions for string extensions. +// +// These functions provide compile-time cost estimates proportional to the size of +// the input string(s), ensuring that the CEL cost system accurately reflects the +// computational work performed by string operations. + +// estimateStringFixedTransformCost estimates cost for O(n) string operations such as +// lowerAscii, upperAsciil, reverse and quote. +func estimateStringFixedTransformCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if target == nil { + return nil + } + cost, size := estimateStringScan(estimateSize(estimator, *target)) + return callEstimate(cost.Add(callCostEstimate).Add(size.AsCost()), size) +} + +// estimateStringVariableTransformCost estimates cost for O(n) string operations that result +// in a variable sized string which may be empty to the exact input string. +func estimateStringVariableTransformCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if target == nil { + return nil + } + cost, size := estimateStringScan(estimateSize(estimator, *target)) + transformSize := rangedSizeEstimate(0, size.Max) + return callEstimate(cost.Add(callCostEstimate).Add(transformSize.AsCost()), &transformSize) +} + +// estimateStringCharAtCost includes a cost of 1 for the allocation, plus the string traversal cost. +func estimateStringCharAtCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if target == nil || len(args) != 1 { + return nil + } + cost, _ := estimateStringScan(estimateSize(estimator, *target)) + resultSize := rangedSizeEstimate(0, 1) + return callEstimate(cost.Add(callCostEstimate).Add(callCostEstimate), &resultSize) +} + +// estimateSubstringCost estimates the cost for an O(n) traversal and allocation. +func estimateSubstringCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if target == nil || len(args) < 1 || len(args) > 2 { + return nil + } + targetSize := estimateSize(estimator, *target) + cost, _ := estimateStringScan(targetSize) + + start := nodeAsUintValue(args[0], 0) + end := targetSize.Max + if len(args) == 2 { + end = nodeAsUintValue(args[1], end) + } + resultSize := fixedSizeEstimate(end - start) + return callEstimate(cost.Add(callCostEstimate).Add(resultSize.AsCost()), &resultSize) +} + +// estimateStringSearchCost estimates cost for O(n*m) string search operations +// such as indexOf and lastIndexOf. +func estimateStringSearchCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if target == nil || len(args) < 1 { + return nil + } + targetSize := estimateSize(estimator, *target) + needleSize := estimateSize(estimator, args[0]) + searchSize := targetSize.Multiply(needleSize) + searchCost, _ := estimateStringScan(searchSize) + // Search cost is proportional to target size * substring size. + return callEstimate(searchCost.Add(callCostEstimate), nil) +} + +// estimateStringReplaceCost estimates cost for string replace operations. +// The cost accounts for search (O(n*m)) and potential output size growth. +func estimateStringReplaceCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if target == nil || len(args) < 2 { + return nil + } + // Compute the search for the replacement string, by 'm' times + targetSize := estimateSize(estimator, *target) + needleSize := atLeastOne(estimateSize(estimator, args[0])) + searchCost := atLeastOne(targetSize).Multiply(needleSize).MultiplyByCostFactor(stringCostFactor) + + replacementSize := estimateSize(estimator, args[1]).Add(fixedSizeEstimate(1)) + allReplacedSize := safeMul(safeAdd(targetSize.Max, 1), replacementSize.Max) + resultMinSize := targetSize.Min + if resultMinSize > replacementSize.Min { + resultMinSize = replacementSize.Min + } + resultSize := rangedSizeEstimate(resultMinSize, allReplacedSize) + return callEstimate( + searchCost.Add(resultSize.AsCost()).Add(callCostEstimate), &resultSize, + ) +} + +// estimateStringSplitCost estimates cost for string split operations. +// Split creates a list of substrings, so cost includes both traversal and +// list allocation proportional to the input size. +func estimateStringSplitCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if target == nil || len(args) < 1 { + return nil + } + targetSize := estimateSize(estimator, *target) + // Traversal cost proportional to input size. + traversalCost := targetSize.Add(fixedSizeEstimate(1)).MultiplyByCostFactor(stringCostFactor) + // Worst case: split("") produces N elements for a string of size N. + resultSize := rangedSizeEstimate(0, targetSize.Max) + // Include list creation base cost plus allocation for each element. + allocationCost := resultSize.MultiplyByCostFactor(1).Add(checker.FixedCostEstimate(common.ListCreateBaseCost)) + cost := traversalCost.Add(allocationCost).Add(callCostEstimate) + return callEstimate(cost, &resultSize) +} + +// estimateStringJoinCost estimates cost for string join operations. +// Join iterates over all list elements and concatenates them, so cost is +// proportional to the total size of all elements plus separator overhead. +func estimateStringJoinCost(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if target == nil { + return nil + } + targetSize := estimateSize(estimator, *target) + sepSize := fixedSizeEstimate(0) + if len(args) >= 1 { + sepSize = estimateSize(estimator, args[0]) + } + // Traversal cost proportional to the number of list elements. + traversalCost := targetSize.Add(fixedSizeEstimate(1)).MultiplyByCostFactor(stringCostFactor) + // Result size: sum of element sizes + (n-1) * separator size. + // Worst case estimate: use list size * max element size + list size * separator size. + maxResultSize := safeAdd(safeMul(targetSize.Max, (safeAdd(1, sepSize.Max))), sepSize.Max) + resultSize := rangedSizeEstimate(0, maxResultSize) + cost := traversalCost.Add(resultSize.MultiplyByCostFactor(1)).Add(callCostEstimate) + return callEstimate(cost, &resultSize) +} + +// Runtime cost tracking functions for string extensions. +// +// These functions compute the actual cost of string operations after evaluation, +// using the real sizes of the inputs and outputs. + +// trackStringCharAtCost tracks runtime cost for O(n) string operations. +func trackStringCharAtCost(args []ref.Val, result ref.Val) *uint64 { + size := float64(actualSize(args[0])) * stringCostFactor + cost := safeAdd(callCost, uint64(math.Ceil(size)), 1) + return &cost +} + +// trackStringTransformCost tracks runtime cost for O(n) string operations. +func trackStringTransformCost(args []ref.Val, result ref.Val) *uint64 { + transformCost := math.Ceil(float64(actualSize(args[0])) * stringCostFactor) + resultSize := actualSize(result) + cost := safeAdd(callCost, uint64(transformCost), resultSize) + return &cost +} + +// trackStringSearchCost tracks runtime cost for O(n*m) string search operations. +func trackStringSearchCost(args []ref.Val, _ ref.Val) *uint64 { + searchCost := float64(actualSize(args[0])*actualSize(args[1])) * stringCostFactor + cost := safeAdd(uint64(math.Ceil(searchCost)), callCost) + return &cost +} + +// trackStringReplaceCost tracks runtime cost for string replace operations, +// accounting for search cost and the size of the result. +func trackStringReplaceCost(args []ref.Val, result ref.Val) *uint64 { + targetSize := actualSize(args[0]) + if targetSize == 0 { + targetSize = 1 + } + needleSize := actualSize(args[1]) + if needleSize == 0 { + needleSize = 1 + } + searchCost := uint64(math.Ceil(float64(targetSize*needleSize) * stringCostFactor)) + cost := safeAdd(callCost, searchCost, actualSize(result)) + return &cost +} + +// trackStringSplitCost tracks runtime cost for string split operations, +// accounting for traversal and list allocation. +func trackStringSplitCost(args []ref.Val, result ref.Val) *uint64 { + traversalCost := float64(safeAdd(actualSize(args[0]), 1)) * stringCostFactor + resultSize := actualSize(result) + cost := safeAdd(callCost, uint64(math.Ceil(traversalCost)), resultSize, common.ListCreateBaseCost) + return &cost +} + +// trackStringJoinCost tracks runtime cost for string join operations, +// accounting for traversal and the size of the result. +func trackStringJoinCost(args []ref.Val, result ref.Val) *uint64 { + traversalCost := float64(safeAdd(actualSize(args[0]), 1)) * stringCostFactor + cost := safeAdd(callCost, uint64(math.Ceil(traversalCost)), actualSize(result)) + return &cost +} diff --git a/vendor/github.com/google/cel-go/interpreter/BUILD.bazel b/vendor/github.com/google/cel-go/interpreter/BUILD.bazel index 220e23d47..40ac2ba69 100644 --- a/vendor/github.com/google/cel-go/interpreter/BUILD.bazel +++ b/vendor/github.com/google/cel-go/interpreter/BUILD.bazel @@ -9,11 +9,13 @@ go_library( name = "go_default_library", srcs = [ "activation.go", + "async.go", "attribute_patterns.go", "attributes.go", "decorators.go", "dispatcher.go", "evalstate.go", + "frame.go", "interpretable.go", "interpreter.go", "optimizations.go", @@ -45,8 +47,10 @@ go_test( name = "go_default_test", srcs = [ "activation_test.go", + "async_test.go", "attribute_patterns_test.go", "attributes_test.go", + "frame_test.go", "interpreter_test.go", "prune_test.go", "runtimecost_test.go", @@ -63,6 +67,7 @@ go_test( "//common/operators:go_default_library", "//common/stdlib:go_default_library", "//common/types:go_default_library", + "//common/types/ref:go_default_library", "//parser:go_default_library", "//test:go_default_library", "//test/proto2pb:go_default_library", diff --git a/vendor/github.com/google/cel-go/interpreter/activation.go b/vendor/github.com/google/cel-go/interpreter/activation.go index dd40619ee..bc9296ed4 100644 --- a/vendor/github.com/google/cel-go/interpreter/activation.go +++ b/vendor/github.com/google/cel-go/interpreter/activation.go @@ -110,8 +110,9 @@ func (a *mapActivation) ResolveName(name string) (any, bool) { // hierarchicalActivation which implements Activation and contains a parent and // child activation. type hierarchicalActivation struct { - parent Activation - child Activation + parent Activation + child Activation + poolAllocated bool } // Parent implements the Activation interface method. @@ -127,10 +128,41 @@ func (a *hierarchicalActivation) ResolveName(name string) (any, bool) { return a.parent.ResolveName(name) } +// Unwrap returns the parent activation, stripping the local child scope. +// This allows global disambiguation to skip past locally introduced variables. +func (a *hierarchicalActivation) Unwrap() Activation { + return a.parent +} + +// IsLocalVariable reports whether the variable name is locally bound in the hierarchical activation. +func (a *hierarchicalActivation) IsLocalVariable(name string) bool { + if holder, ok := a.child.(localVariableHolder); ok { + if holder.IsLocalVariable(name) { + return true + } + } + if holder, ok := a.parent.(localVariableHolder); ok { + return holder.IsLocalVariable(name) + } + return false +} + +// AsPartialActivation checks the child first via direct type assertion (to +// avoid recursion through the folder → frame → hierarchicalActivation cycle), +// then walks the parent hierarchy via the free function. +func (a *hierarchicalActivation) AsPartialActivation() (PartialActivation, bool) { + if pv, ok := a.child.(partialActivationConverter); ok { + if p, ok := pv.AsPartialActivation(); ok { + return p, true + } + } + return AsPartialActivation(a.parent) +} + // NewHierarchicalActivation takes two activations and produces a new one which prioritizes // resolution in the child first and parent(s) second. func NewHierarchicalActivation(parent Activation, child Activation) Activation { - return &hierarchicalActivation{parent, child} + return &hierarchicalActivation{parent: parent, child: child, poolAllocated: false} } // NewPartialActivation returns an Activation which contains a list of AttributePattern values diff --git a/vendor/github.com/google/cel-go/interpreter/async.go b/vendor/github.com/google/cel-go/interpreter/async.go new file mode 100644 index 000000000..4e391196b --- /dev/null +++ b/vendor/github.com/google/cel-go/interpreter/async.go @@ -0,0 +1,530 @@ +// Copyright 2026 Google LLC +// +// 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 interpreter + +import ( + "context" + "encoding/binary" + "fmt" + "hash/fnv" + "math" + "sync" + "sync/atomic" + + "github.com/google/cel-go/common/functions" + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" +) + +// Async extension function support. +// +// CEL supports `types.Unknown` as a first-class value, and concurrent (async) function execution +// in CEL invokes a stub function which checks for the presence of an existing result which matches +// the function call and call arguments, or which records the 'unexecuted' function and call arguments +// for concurrent execution in a later phase if the result is `types.Unknown` and indicates the +// expression ids of the functions necessary to advance the execution. +// +// This call pattern is repeated iteratively until there are either no more functions to call or no +// progress is made toward resolving the unknowns. + +// AsyncObserver provides callbacks for monitoring the lifecycle of asynchronous function calls. +// +// Implementations must be safe for concurrent use: OnCallStarted is invoked from the evaluator +// goroutine when a call is launched, while OnCallFinished is invoked from the call's own goroutine +// when it completes. The two callbacks therefore run on different goroutines, and OnCallFinished +// callbacks for distinct calls may run concurrently with each other. +type AsyncObserver interface { + // OnCallStarted is called when an asynchronous function is first launched. + OnCallStarted(callID int64, function, overload string, args []ref.Val) + // OnCallFinished is called when an asynchronous function completes. + OnCallFinished(callID int64, function, overload string, res ref.Val) +} + +// AsyncCall describes a pending or completed asynchronous function call. +type AsyncCall interface { + // CallID returns the unique identifier for this async call invocation. + CallID() int64 + // Function returns the name of the function being called. + Function() string + // Overload returns the specific overload ID being invoked. + Overload() string +} + +// evalAsyncFunc is the planned Interpretable for an asynchronous function call. +type evalAsyncFunc struct { + id int64 + function string + overload string + args []InterpretableV2 + impl functions.AsyncOp +} + +// ID implements the Interpretable interface method. +func (fn *evalAsyncFunc) ID() int64 { + return fn.id +} + +// Function returns the name of the function being invoked. +func (fn *evalAsyncFunc) Function() string { + return fn.function +} + +// OverloadID returns the overload id of the function being invoked. +func (fn *evalAsyncFunc) OverloadID() string { + return fn.overload +} + +// Args returns the argument Interpretables for the function call. +func (fn *evalAsyncFunc) Args() []InterpretableV2 { + return fn.args +} + +// Eval implements the Interpretable interface method. +func (fn *evalAsyncFunc) Eval(vars Activation) ref.Val { + return fn.Exec(AsFrame(vars)) +} + +// Exec implements the InterpretableV2 interface method. +func (fn *evalAsyncFunc) Exec(frame *ExecutionFrame) ref.Val { + argVals := make([]ref.Val, len(fn.args)) + var unk *types.Unknown + for i, arg := range fn.args { + argVals[i] = arg.Exec(frame) + if types.IsError(argVals[i]) { + return argVals[i] + } + unk, _ = types.MaybeMergeUnknowns(argVals[i], unk) + } + if unk != nil { + return unk + } + result := frame.ComputeResult(fn.ID(), fn.Function(), fn.OverloadID(), fn.impl, argVals) + return types.LabelErrNode(fn.id, result) +} + +// asyncCallStateTracker manages async call states across re-evaluations of a single program. +type asyncCallStateTracker struct { + mu sync.RWMutex + // calls buckets call states by a composite hash of (node id, overload, string/int/double/uint/bool args). + // A single AST node id may host many concurrently-live calls when it is evaluated inside a + // comprehension (once per element with different arguments), so each bucket may hold more + // than one state. The exact match within a bucket is resolved via asyncCallState.matches, + // which applies CEL's full equality semantics to the arguments. + calls map[uint64][]*asyncCallState + callsByID map[int64]*asyncCallState + nextCallID atomic.Int64 +} + +func newAsyncCallStateTracker() *asyncCallStateTracker { + return &asyncCallStateTracker{ + calls: make(map[uint64][]*asyncCallState), + callsByID: make(map[int64]*asyncCallState), + } +} + +var ( + hashZeroMarker = []byte{0} + hashStringMarker = []byte{'s'} + hashBoolTrueMarker = []byte{'b', 1} + hashBoolFalseMarker = []byte{'b', 0} + hashNumberMarker = []byte{'n'} + hashDefaultMarker = []byte{'x'} +) + +// hashCall computes the composite bucket key for an async call. +// +// Only string, int, double, uint, and bool argument values contribute to the hash. More complex types +// rely on a richer notion of equivalence (e.g. unordered maps, proto equality, custom types) +// that a byte-level hash cannot capture safely, so they are intentionally excluded from the key +// and are instead disambiguated within the bucket by asyncCallState.matches. +func hashCall(id int64, overload string, args []ref.Val) uint64 { + h := fnv.New64a() + var idBuf [8]byte + binary.LittleEndian.PutUint64(idBuf[:], uint64(id)) + h.Write(idBuf[:]) + h.Write([]byte(overload)) + h.Write(hashZeroMarker) + for _, arg := range args { + switch v := arg.(type) { + case types.String: + h.Write(hashStringMarker) + h.Write([]byte(string(v))) + case types.Bool: + if bool(v) { + h.Write(hashBoolTrueMarker) + } else { + h.Write(hashBoolFalseMarker) + } + case types.Int: + h.Write(hashNumberMarker) + var buf [8]byte + binary.LittleEndian.PutUint64(buf[:], math.Float64bits(float64(v))) + h.Write(buf[:]) + case types.Uint: + h.Write(hashNumberMarker) + var buf [8]byte + binary.LittleEndian.PutUint64(buf[:], math.Float64bits(float64(v))) + h.Write(buf[:]) + case types.Double: + h.Write(hashNumberMarker) + if math.IsNaN(float64(v)) { + h.Write([]byte("NaN")) + h.Write(hashZeroMarker) + continue + } + // Normalize -0.0 to 0.0. Go will treat -0.0 as 0.0 at compile time, + // but the function math.Copysign(0.0, -1.0) can be used to test the -0.0 case. + if v == types.Double(0.0) && math.Signbit(float64(v)) { + v = types.Double(0.0) + } + var buf [8]byte + binary.LittleEndian.PutUint64(buf[:], math.Float64bits(float64(v))) + h.Write(buf[:]) + default: + // Value intentionally omitted; bucket membership falls back to matches. + h.Write(hashDefaultMarker) + } + // Separator to avoid cross-argument collisions, e.g. ("a", "bc") vs ("ab", "c"). + h.Write(hashZeroMarker) + } + return h.Sum64() +} + +// findInBucket returns the call state in the bucket matching the same node id and call identity, +// or nil if no match is present. +func findInBucket(bucket []*asyncCallState, id int64, function, overload string, args []ref.Val) *asyncCallState { + for _, acs := range bucket { + if acs.matches(id, function, overload, args) { + return acs + } + } + return nil +} + +// getOrCreate returns the existing call state for the (node id, args) tuple, or registers and +// returns a new one. A newly registered call is assigned a unique callID and counted as pending. +func (t *asyncCallStateTracker) getOrCreate(id int64, function, overload string, argVals []ref.Val, impl functions.AsyncOp, gate *asyncGate) *asyncCallState { + key := hashCall(id, overload, argVals) + + t.mu.RLock() + acs := findInBucket(t.calls[key], id, function, overload, argVals) + t.mu.RUnlock() + if acs != nil { + return acs + } + + t.mu.Lock() + defer t.mu.Unlock() + // Check again in case it was created while waiting for the lock. + if acs := findInBucket(t.calls[key], id, function, overload, argVals); acs != nil { + return acs + } + + // Assign a new unique call ID for this async call. + acs = newAsyncCallState(id, function, overload, argVals, impl) + callID := t.nextCallID.Add(1) + acs.callID = callID + acs.gate = gate + t.calls[key] = append(t.calls[key], acs) + t.callsByID[callID] = acs + return acs +} + +func (t *asyncCallStateTracker) getByID(callID int64) *asyncCallState { + t.mu.RLock() + defer t.mu.RUnlock() + return t.callsByID[callID] +} + +func newAsyncCallState(id int64, function, overload string, argVals []ref.Val, impl functions.AsyncOp) *asyncCallState { + return &asyncCallState{ + id: id, + function: function, + overload: overload, + argVals: argVals, + impl: impl, + } +} + +// asyncCallState tracks the result of a single async function call across multiple re-evaluations. +type asyncCallState struct { + id int64 // AST expression node ID where the call is defined. + callID int64 // Unique incremental tracking ID assigned to this call. + function string + overload string + argVals []ref.Val + impl functions.AsyncOp + + mu sync.RWMutex + started bool + result ref.Val + + gate *asyncGate +} + +// CallID returns the unique identifier for this async call invocation. +func (acs *asyncCallState) CallID() int64 { + return acs.callID +} + +// Function returns the name of the function being called. +func (acs *asyncCallState) Function() string { + return acs.function +} + +// Overload returns the specific overload ID being invoked. +func (acs *asyncCallState) Overload() string { + return acs.overload +} + +// ResultOrUnknown returns the cached result if the call has completed, an Unknown +// with the call ID if pending, or nil if the call has not been started. +func (acs *asyncCallState) ResultOrUnknown() ref.Val { + if acs == nil { + return nil + } + acs.mu.RLock() + defer acs.mu.RUnlock() + if acs.result == nil && acs.started { + return types.NewUnknown(acs.callID, nil) + } + return acs.result +} + +// SetResult sets the completed result for an asynchronous function call. +func (acs *asyncCallState) SetResult(res ref.Val) { + if acs == nil { + return + } + acs.mu.Lock() + defer acs.mu.Unlock() + acs.result = res +} + +// launch returns a call's cached result, or starts the call (subject to the launch limiter) and +// returns an Unknown referencing its callID while the result is pending. +// +// Admission control: when a concurrency semaphore is configured, a launch slot is reserved with a +// non-blocking send. If no slot is free the call is left unstarted and an Unknown is returned; the +// call is retried on a later re-evaluation pass once an in-flight call completes and frees a slot. +// The reservation is non-blocking on purpose — the evaluator runs on a single goroutine, and +// blocking it here while completing calls block on an undrained completion channel would deadlock. +// The slot is held by the launched goroutine and released when it exits, so the number of live +// async goroutines is bounded by the semaphore capacity. +func (t *asyncCallStateTracker) launch(ctx context.Context, acs *asyncCallState, observer AsyncObserver) ref.Val { + if res := acs.ResultOrUnknown(); res != nil { + return res + } + gate := acs.gate + if !gate.TryAcquire() { + return types.NewUnknown(acs.callID, nil) + } + acs.mu.Lock() + if acs.started || acs.result != nil { + // Defensive: the evaluator is single-threaded so this should not happen, but if it does, + // return the reserved slot rather than leak it. + acs.mu.Unlock() + gate.Release() + return types.NewUnknown(acs.callID, nil) + } + acs.started = true + acs.mu.Unlock() + + if observer != nil { + observer.OnCallStarted(acs.callID, acs.function, acs.overload, acs.argVals) + } + go func() { + defer func() { + if observer != nil { + observer.OnCallFinished(acs.callID, acs.function, acs.overload, acs.ResultOrUnknown()) + } + gate.Complete(ctx, acs.callID) + }() + + ch := acs.impl(ctx, acs.argVals...) + // Early terminate with a CEL error when an implementation returns an empty channel. + if ch == nil { + acs.SetResult(types.NewErrFromString( + fmt.Sprintf("function %s returned an empty channel", acs.function))) + return + } + // Wait for the async computation to finish or for the context to be cancelled. + select { + case r, ok := <-ch: + if !ok { + acs.SetResult(types.NewErrFromString( + fmt.Sprintf("function %s returned an empty channel", acs.function))) + return + } + acs.SetResult(r) + case <-ctx.Done(): + // Evaluation context cancelled before the async operation completed. + acs.SetResult(types.WrapErr(context.Cause(ctx))) + } + }() + return types.NewUnknown(acs.callID, nil) +} + +// matches reports whether two call states refer to the same function, overload, and arguments. +func (acs *asyncCallState) matches(id int64, function, overload string, args []ref.Val) bool { + if acs == nil { + return false + } + if acs.id != id || acs.function != function || acs.overload != overload { + return false + } + if len(acs.argVals) != len(args) { + return false + } + for i, v := range acs.argVals { + otherV := args[i] + if types.Equal(v, otherV) == types.True { + continue + } + if n, ok := v.(types.Double); ok { + // Treat NaN as equivalent for the sake of function dispatch equality. + if otherN, ok := otherV.(types.Double); ok && math.IsNaN(float64(n)) && math.IsNaN(float64(otherN)) { + continue + } + } + return false + } + return true +} + +// trackerShrinkThreshold is the entry count above which a released tracker's maps are reallocated +// rather than cleared in place, so the pool does not retain a large backing array indefinitely. +const trackerShrinkThreshold = 1024 + +// asyncCallStateTrackerPool provides a synchronized pool of asyncCallStateTrackers. +type asyncCallTrackerPool struct { + sync.Pool +} + +func (pool *asyncCallTrackerPool) create() *asyncCallStateTracker { + return pool.Get().(*asyncCallStateTracker) +} + +func (pool *asyncCallTrackerPool) release(tracker *asyncCallStateTracker) { + if tracker == nil { + return + } + tracker.mu.Lock() + // Clearing with delete reuses the backing arrays, which is ideal for the common case but pins + // a large allocation in the pool after a wide fan-out (e.g. an async call over a big list). + // Past a threshold, reallocate so the high-water-mark memory is released to the GC instead of + // being retained by the pooled tracker. + if len(tracker.calls) > trackerShrinkThreshold || len(tracker.callsByID) > trackerShrinkThreshold { + tracker.calls = make(map[uint64][]*asyncCallState) + tracker.callsByID = make(map[int64]*asyncCallState) + } else { + for k := range tracker.calls { + delete(tracker.calls, k) + } + for k := range tracker.callsByID { + delete(tracker.callsByID, k) + } + } + tracker.nextCallID.Store(0) + tracker.mu.Unlock() + pool.Pool.Put(tracker) +} + +func newAsyncCallTrackerPool() *asyncCallTrackerPool { + return &asyncCallTrackerPool{ + Pool: sync.Pool{ + New: func() any { + return newAsyncCallStateTracker() + }, + }, + } +} + +var asyncCallStateTrackerPool = newAsyncCallTrackerPool() + +// asyncGate coordinates async call admission control and completion signaling. +type asyncGate struct { + semaphore chan struct{} + completions chan<- int64 + activeCalls atomic.Int32 +} + +func newAsyncGate(maxConcurrency int, completions chan<- int64) *asyncGate { + var sem chan struct{} + if maxConcurrency > 0 { + sem = make(chan struct{}, maxConcurrency) + } + return &asyncGate{ + semaphore: sem, + completions: completions, + } +} + +// TryAcquire attempts to acquire a concurrency slot and increments the active calls count. +func (g *asyncGate) TryAcquire() bool { + if g == nil { + return true + } + if g.semaphore != nil { + select { + case g.semaphore <- struct{}{}: + default: + return false + } + } + g.activeCalls.Add(1) + return true +} + +// Release releases a concurrency slot and decrements the active calls count (used for defensive recovery). +func (g *asyncGate) Release() { + if g == nil { + return + } + if g.semaphore != nil { + select { + case <-g.semaphore: + default: + } + } + g.activeCalls.Add(-1) +} + +// Complete releases a concurrency slot and notifies completions. +func (g *asyncGate) Complete(ctx context.Context, callID int64) { + if g == nil { + return + } + g.Release() + + if g.completions != nil { + // Prioritize context cancellation to prevent racy completion signals. + if ctx.Err() != nil { + return + } + select { + case g.completions <- callID: + case <-ctx.Done(): + } + } +} + +// ActiveCalls returns the number of active asynchronous calls. +func (g *asyncGate) ActiveCalls() int { + if g == nil { + return 0 + } + return int(g.activeCalls.Load()) +} diff --git a/vendor/github.com/google/cel-go/interpreter/attribute_patterns.go b/vendor/github.com/google/cel-go/interpreter/attribute_patterns.go index 7d0759e37..bbaca5226 100644 --- a/vendor/github.com/google/cel-go/interpreter/attribute_patterns.go +++ b/vendor/github.com/google/cel-go/interpreter/attribute_patterns.go @@ -16,6 +16,7 @@ package interpreter import ( "fmt" + "strings" "github.com/google/cel-go/common/containers" "github.com/google/cel-go/common/types" @@ -207,10 +208,19 @@ func (fac *partialAttributeFactory) AbsoluteAttribute(id int64, names ...string) // 'maybe' NamespacedAttribute values are produced using the partialAttributeFactory rather than // the base AttributeFactory implementation. func (fac *partialAttributeFactory) MaybeAttribute(id int64, name string) Attribute { + var names []string + // When there's a single name with a dot prefix, it indicates that the 'maybe' attribute is a + // globally namespaced identifier. + if strings.HasPrefix(name, ".") { + names = append(names, name) + } else { + // In all other cases, the candidate names should be inferred. + names = fac.container.ResolveCandidateNames(name) + } return &maybeAttribute{ id: id, attrs: []NamespacedAttribute{ - fac.AbsoluteAttribute(id, fac.container.ResolveCandidateNames(name)...), + fac.AbsoluteAttribute(id, names...), }, adapter: fac.adapter, provider: fac.provider, @@ -245,6 +255,9 @@ func (fac *partialAttributeFactory) matchesUnknownPatterns( patterns := vars.UnknownAttributePatterns() candidateIndices := map[int]struct{}{} for _, variable := range variableNames { + if holder, ok := vars.(localVariableHolder); ok && holder.IsLocalVariable(variable) { + continue + } for i, pat := range patterns { if pat.VariableMatches(variable) { if len(qualifiers) == 0 { diff --git a/vendor/github.com/google/cel-go/interpreter/attributes.go b/vendor/github.com/google/cel-go/interpreter/attributes.go index b1b3aacc8..26d8eb0f3 100644 --- a/vendor/github.com/google/cel-go/interpreter/attributes.go +++ b/vendor/github.com/google/cel-go/interpreter/attributes.go @@ -166,9 +166,17 @@ type attrFactory struct { // The namespaceNames represent the names the variable could have based on namespace // resolution rules. func (r *attrFactory) AbsoluteAttribute(id int64, names ...string) NamespacedAttribute { + disambiguateNames := false + for idx, name := range names { + if strings.HasPrefix(name, ".") { + disambiguateNames = true + names[idx] = strings.TrimPrefix(name, ".") + } + } return &absoluteAttribute{ id: id, namespaceNames: names, + disambiguateNames: disambiguateNames, qualifiers: []Qualifier{}, adapter: r.adapter, provider: r.provider, @@ -182,7 +190,7 @@ func (r *attrFactory) AbsoluteAttribute(id int64, names ...string) NamespacedAtt func (r *attrFactory) ConditionalAttribute(id int64, expr Interpretable, t, f Attribute) Attribute { return &conditionalAttribute{ id: id, - expr: expr, + expr: adaptToV2(expr), truthy: t, falsy: f, adapter: r.adapter, @@ -193,10 +201,19 @@ func (r *attrFactory) ConditionalAttribute(id int64, expr Interpretable, t, f At // MaybeAttribute collects variants of unchecked AbsoluteAttribute values which could either be // direct variable accesses or some combination of variable access with qualification. func (r *attrFactory) MaybeAttribute(id int64, name string) Attribute { + var names []string + // When there's a single name with a dot prefix, it indicates that the 'maybe' attribute is a + // globally namespaced identifier. + if strings.HasPrefix(name, ".") { + names = append(names, name) + } else { + // In all other cases, the candidate names should be inferred. + names = r.container.ResolveCandidateNames(name) + } return &maybeAttribute{ id: id, attrs: []NamespacedAttribute{ - r.AbsoluteAttribute(id, r.container.ResolveCandidateNames(name)...), + r.AbsoluteAttribute(id, names...), }, adapter: r.adapter, provider: r.provider, @@ -208,7 +225,7 @@ func (r *attrFactory) MaybeAttribute(id int64, name string) Attribute { func (r *attrFactory) RelativeAttribute(id int64, operand Interpretable) Attribute { return &relativeAttribute{ id: id, - operand: operand, + operand: adaptToV2(operand), qualifiers: []Qualifier{}, adapter: r.adapter, fac: r, @@ -242,10 +259,13 @@ type absoluteAttribute struct { // namespaceNames represent the names the variable could have based on declared container // (package) of the expression. namespaceNames []string - qualifiers []Qualifier - adapter types.Adapter - provider types.Provider - fac AttributeFactory + // disambiguateNames indicates whether the namespaceNames require disambiguation with local variables. + disambiguateNames bool + + qualifiers []Qualifier + adapter types.Adapter + provider types.Provider + fac AttributeFactory errorOnBadPresenceTest bool } @@ -304,15 +324,34 @@ func (a *absoluteAttribute) String() string { // a type, then the result is `nil`, `error` with the error indicating the name of the first // variable searched as missing. func (a *absoluteAttribute) Resolve(vars Activation) (any, error) { + // unwrap any local activations to ensure that we reach the variables provided as input + // to the expression in the event that we need to disambiguate between global and local + // variables. + // + // Presently, only dynamic and constant slot activations created during comprehensions + // support 'unwrapping', which is consistent with how local variables are introduced into CEL. + var inputVars Activation + if a.disambiguateNames { + inputVars = vars + wrapped, ok := inputVars.(activationWrapper) + for ok { + inputVars = wrapped.Unwrap() + wrapped, ok = inputVars.(activationWrapper) + } + } for _, nm := range a.namespaceNames { // If the variable is found, process it. Otherwise, wait until the checks to // determine whether the type is unknown before returning. - obj, found := vars.ResolveName(nm) + v := vars + if a.disambiguateNames { + v = inputVars + } + obj, found := v.ResolveName(nm) if found { if celErr, ok := obj.(*types.Err); ok { - return nil, celErr.Unwrap() + return nil, celErr } - obj, isOpt, err := applyQualifiers(vars, obj, a.qualifiers) + obj, isOpt, err := applyQualifiers(v, obj, a.qualifiers) if err != nil { return nil, err } @@ -345,7 +384,7 @@ func (a *absoluteAttribute) Resolve(vars Activation) (any, error) { type conditionalAttribute struct { id int64 - expr Interpretable + expr InterpretableV2 truthy Attribute falsy Attribute adapter types.Adapter @@ -532,7 +571,7 @@ func (a *maybeAttribute) String() string { type relativeAttribute struct { id int64 - operand Interpretable + operand InterpretableV2 qualifiers []Qualifier adapter types.Adapter fac AttributeFactory @@ -925,9 +964,11 @@ func (q *intQualifier) qualifyInternal(vars Activation, obj any, presenceTest, p } case map[int32]any: isMap = true - obj, isKey := o[int32(i)] - if isKey { - return obj, true, nil + if i32 := int32(i); int64(i32) == i { + obj, isKey := o[i32] + if isKey { + return obj, true, nil + } } case map[int64]any: isMap = true @@ -1050,9 +1091,11 @@ func (q *uintQualifier) qualifyInternal(vars Activation, obj any, presenceTest, return obj, true, nil } case map[uint32]any: - obj, isKey := o[uint32(u)] - if isKey { - return obj, true, nil + if u32 := uint32(u); uint64(u32) == u { + obj, isKey := o[u32] + if isKey { + return obj, true, nil + } } case map[uint64]any: obj, isKey := o[u] @@ -1262,7 +1305,7 @@ func applyQualifiers(vars Activation, obj any, qualifiers []Qualifier) (any, boo if !optObj.HasValue() { return optObj, false, nil } - obj = optObj.GetValue().Value() + obj = optObj.GetValue() } var err error diff --git a/vendor/github.com/google/cel-go/interpreter/decorators.go b/vendor/github.com/google/cel-go/interpreter/decorators.go index 502db35fc..6c48e5c1b 100644 --- a/vendor/github.com/google/cel-go/interpreter/decorators.go +++ b/vendor/github.com/google/cel-go/interpreter/decorators.go @@ -15,6 +15,8 @@ package interpreter import ( + "fmt" + "github.com/google/cel-go/common/overloads" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" @@ -25,9 +27,13 @@ import ( // Interpretable expression nodes at construction time. type InterpretableDecorator func(Interpretable) (Interpretable, error) +// InterpretableDecoratorV2 is a functional interface for decorating or replacing +// InterpretableV2 expression nodes at construction time. +type InterpretableDecoratorV2 func(InterpretableV2) (InterpretableV2, error) + // decObserveEval records evaluation state into an EvalState object. -func decObserveEval(observer EvalObserver) InterpretableDecorator { - return func(i Interpretable) (Interpretable, error) { +func decObserveEval(observer EvalObserver) InterpretableDecoratorV2 { + return func(i InterpretableV2) (InterpretableV2, error) { switch inst := i.(type) { case *evalWatch, *evalWatchAttr, *evalWatchConst, *evalWatchConstructor: // these instruction are already watching, return straight-away. @@ -49,8 +55,8 @@ func decObserveEval(observer EvalObserver) InterpretableDecorator { }, nil default: return &evalWatch{ - Interpretable: i, - observer: observer, + InterpretableV2: i, + observer: observer, }, nil } } @@ -58,8 +64,8 @@ func decObserveEval(observer EvalObserver) InterpretableDecorator { // decInterruptFolds creates an intepretable decorator which marks comprehensions as interruptable // where the interrupt state is communicated via a hidden variable on the Activation. -func decInterruptFolds() InterpretableDecorator { - return func(i Interpretable) (Interpretable, error) { +func decInterruptFolds() InterpretableDecoratorV2 { + return func(i InterpretableV2) (InterpretableV2, error) { fold, ok := i.(*evalFold) if !ok { return i, nil @@ -70,8 +76,8 @@ func decInterruptFolds() InterpretableDecorator { } // decDisableShortcircuits ensures that all branches of an expression will be evaluated, no short-circuiting. -func decDisableShortcircuits() InterpretableDecorator { - return func(i Interpretable) (Interpretable, error) { +func decDisableShortcircuits() InterpretableDecoratorV2 { + return func(i InterpretableV2) (InterpretableV2, error) { switch expr := i.(type) { case *evalOr: return &evalExhaustiveOr{ @@ -104,8 +110,8 @@ func decDisableShortcircuits() InterpretableDecorator { // conditionally precomputing the result. // - build list and map values with constant elements. // - convert 'in' operations to set membership tests if possible. -func decOptimize() InterpretableDecorator { - return func(i Interpretable) (Interpretable, error) { +func decOptimize() InterpretableDecoratorV2 { + return func(i InterpretableV2) (InterpretableV2, error) { switch inst := i.(type) { case *evalList: return maybeBuildListLiteral(i, inst) @@ -124,7 +130,7 @@ func decOptimize() InterpretableDecorator { } // decRegexOptimizer compiles regex pattern string constants. -func decRegexOptimizer(regexOptimizations ...*RegexOptimization) InterpretableDecorator { +func decRegexOptimizer(regexOptimizations ...*RegexOptimization) InterpretableDecoratorV2 { functionMatchMap := make(map[string]*RegexOptimization) overloadMatchMap := make(map[string]*RegexOptimization) for _, m := range regexOptimizations { @@ -134,7 +140,7 @@ func decRegexOptimizer(regexOptimizations ...*RegexOptimization) InterpretableDe } } - return func(i Interpretable) (Interpretable, error) { + return func(i InterpretableV2) (InterpretableV2, error) { call, ok := i.(InterpretableCall) if !ok { return i, nil @@ -165,7 +171,78 @@ func decRegexOptimizer(regexOptimizations ...*RegexOptimization) InterpretableDe } } -func maybeOptimizeConstUnary(i Interpretable, call InterpretableCall) (Interpretable, error) { +func decRegexProgramSizeLimit(limit int) InterpretableDecoratorV2 { + return func(i InterpretableV2) (InterpretableV2, error) { + if limit <= 0 { + return i, nil + } + call, ok := i.(InterpretableCall) + if !ok { + return i, nil + } + if !isRegexFunction(call.Function(), call.OverloadID()) || len(call.Args()) < 2 { + return i, nil + } + regexArg := call.Args()[1] + if constVal, isConst := regexArg.(InterpretableConst); isConst { + if pattern, ok := constVal.Value().(types.String); ok { + sz, err := types.RegexProgramSize(string(pattern)) + if err != nil { + return i, nil + } + if sz > limit { + return nil, fmt.Errorf("regex program size %d exceeds limit of %d", sz, limit) + } + } + return i, nil + } + return ®exLimitCall{InterpretableCall: call, limit: limit}, nil + } +} + +func isRegexFunction(fn, overload string) bool { + switch fn { + case overloads.Matches, "regex.extract", "regex.extractAll", "regex.replace": + return true + } + switch overload { + case overloads.Matches, overloads.MatchesString, + "regex_extract_string_string", "regex_extractAll_string_string", + "regex_replace_string_string_string", "regex_replace_string_string_string_int": + return true + } + return false +} + +type regexLimitCall struct { + InterpretableCall + limit int +} + +func (r *regexLimitCall) Exec(frame *ExecutionFrame) ref.Val { + args := r.Args() + if len(args) >= 2 { + patternVal := args[1].Exec(frame) + if types.IsError(patternVal) { + return patternVal + } + if types.IsUnknown(patternVal) { + return patternVal + } + if pat, ok := patternVal.(types.String); ok { + sz, err := types.RegexProgramSize(string(pat)) + if err != nil { + return types.WrapErr(err) + } + if sz > r.limit { + return types.WrapErr(fmt.Errorf("regex program size %d exceeds limit of %d", sz, r.limit)) + } + } + } + return r.InterpretableCall.Exec(frame) +} + +func maybeOptimizeConstUnary(i InterpretableV2, call InterpretableCall) (InterpretableV2, error) { args := call.Args() if len(args) != 1 { return i, nil @@ -181,7 +258,7 @@ func maybeOptimizeConstUnary(i Interpretable, call InterpretableCall) (Interpret return NewConstValue(call.ID(), val), nil } -func maybeBuildListLiteral(i Interpretable, l *evalList) (Interpretable, error) { +func maybeBuildListLiteral(i InterpretableV2, l *evalList) (InterpretableV2, error) { for _, elem := range l.elems { _, isConst := elem.(InterpretableConst) if !isConst { @@ -191,7 +268,7 @@ func maybeBuildListLiteral(i Interpretable, l *evalList) (Interpretable, error) return NewConstValue(l.ID(), l.Eval(EmptyActivation())), nil } -func maybeBuildMapLiteral(i Interpretable, mp *evalMap) (Interpretable, error) { +func maybeBuildMapLiteral(i InterpretableV2, mp *evalMap) (InterpretableV2, error) { for idx, key := range mp.keys { _, isConst := key.(InterpretableConst) if !isConst { @@ -209,7 +286,7 @@ func maybeBuildMapLiteral(i Interpretable, mp *evalMap) (Interpretable, error) { // test if the following conditions are true: // - the list is a constant with homogeneous element types. // - the elements are all of primitive type. -func maybeOptimizeSetMembership(i Interpretable, inlist InterpretableCall) (Interpretable, error) { +func maybeOptimizeSetMembership(i InterpretableV2, inlist InterpretableCall) (InterpretableV2, error) { args := inlist.Args() lhs := args[0] rhs := args[1] diff --git a/vendor/github.com/google/cel-go/interpreter/frame.go b/vendor/github.com/google/cel-go/interpreter/frame.go new file mode 100644 index 000000000..20ab313c8 --- /dev/null +++ b/vendor/github.com/google/cel-go/interpreter/frame.go @@ -0,0 +1,445 @@ +// Copyright 2026 Google LLC +// +// 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 interpreter + +import ( + "context" + "errors" + "fmt" + "sync" + "sync/atomic" + + "github.com/google/cel-go/common/functions" + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" +) + +// evalContext contains the stateful information needed for a single evaluation. +// +// This state is shared across all frames within a single evaluation, including +// child frames created for comprehension blocks. +type evalContext struct { + // interrupt exposes a callback channel for cancellation. + interrupt <-chan struct{} + + // interruptCheckCount is the number of times the interrupt channel has been checked. + interruptCheckCount atomic.Uint64 + + // interruptCheckFrequency is the frequency at which the interrupt channel is checked. + interruptCheckFrequency uint + + // interrupted indicates whether the evaluation has been interrupted. + interrupted atomic.Bool + + // state provides the context for tracking the evaluation state. + state EvalState + + // costs provides the context for tracking the evaluation costs. + costs *CostTracker + + // ctx is the context for async call implementations to use. + ctx context.Context + + // cancel cancels the context when the evaluation is finished. + cancel context.CancelFunc + + // asyncCalls tracks the state of async call invocations across re-evaluations. + asyncCalls *asyncCallStateTracker + + // gate coordinates async call admission control and completion signaling. + gate *asyncGate + + // observer for monitoring async calls. + observer AsyncObserver +} + +// ExecutionFrame provides the context for a single evaluation of an expression. +// +// The execution frame must not be stored in any fashion as its lifecycle is completely +// controlled by the CEL evaluation process. +type ExecutionFrame struct { + // Activation provides the context for resolving variables by name. + Activation + + // parent provides the context for parent scopes (used for comprehension iterators). + parent *ExecutionFrame + + // ctx provides the shared evaluation state across frames. + ctx *evalContext +} + +// NewExecutionFrame creates a new execution frame from the pool. +func NewExecutionFrame(input any) (*ExecutionFrame, error) { + f := frameStack.Get().(*ExecutionFrame) + switch v := input.(type) { + case Activation: + f.Activation = v + case map[string]any: + f.Activation = activationInput.create(v) + default: + return nil, fmt.Errorf("invalid input, wanted Activation or map[string]any, got: (%T)%v", input, input) + } + return f, nil +} + +// SetContext sets the context for the execution frame. +func (f *ExecutionFrame) SetContext(ctx context.Context, interruptCheckFrequency uint) error { + if f.parent != nil { + return errors.New("SetContext() called on child frame") + } + if f.ctx != nil { + return errors.New("SetContext() called more than once") + } + f.ctx = evalContextPool.Get().(*evalContext) + f.ctx.ctx, f.ctx.cancel = context.WithCancel(ctx) + f.ctx.asyncCalls = asyncCallStateTrackerPool.create() + f.ctx.gate = &asyncGate{} + f.ctx.interrupt = ctx.Done() + f.ctx.interruptCheckFrequency = interruptCheckFrequency + f.ctx.interruptCheckCount.Store(0) + f.ctx.interrupted.Store(false) + return nil +} + +// Close releases the resources held by the execution frame and returns it to the pool. +func (f *ExecutionFrame) Close() { + if f.parent == nil && f.ctx != nil { + if f.ctx.cancel != nil { + f.ctx.cancel() + f.ctx.cancel = nil + } + f.ctx.ctx = nil + f.ctx.gate = nil + asyncCallStateTrackerPool.release(f.ctx.asyncCalls) + f.ctx.asyncCalls = nil + f.ctx.observer = nil + f.ctx.interrupt = nil + f.ctx.state = nil + f.ctx.costs = nil + f.ctx.interrupted.Store(false) + f.ctx.interruptCheckCount.Store(0) + f.ctx.interruptCheckFrequency = 0 + evalContextPool.Put(f.ctx) + } + f.ctx = nil + f.parent = nil + if f.Activation != nil { + switch a := f.Activation.(type) { + case *hierarchicalActivation: + if child, ok := a.child.(*inputActivation); ok { + activationInput.release(child) + } + activationStack.release(a) + case *inputActivation: + activationInput.release(a) + } + f.Activation = nil + frameStack.Put(f) + } +} + +// Push pushes the given activation onto the activation stack and returns the new frame. +// +// This operation is internal to the interpreter and is used to handle comprehension +// scoping. The child frame inherits the shared evalContext from the parent. +func (f *ExecutionFrame) Push(activation Activation) *ExecutionFrame { + child := frameStack.Get().(*ExecutionFrame) + child.parent = f + child.ctx = f.ctx + child.Activation = activationStack.create(f.Activation, activation) + return child +} + +// Pop returns the parent frame, releasing the current frame back to the pool. +func (f *ExecutionFrame) Pop() *ExecutionFrame { + if f.parent == nil { + return f + } + parent := f.parent + activationStack.release(f.Activation) + f.Activation = nil + f.parent = nil + f.ctx = nil + frameStack.Put(f) + return parent +} + +// ResolveName implements the Activation interface by proxying to the internal activation. +func (f *ExecutionFrame) ResolveName(name string) (any, bool) { + return f.Activation.ResolveName(name) +} + +// Parent implements the Activation interface by proxying to the internal activation. +func (f *ExecutionFrame) Parent() Activation { + return f.Activation.Parent() +} + +// AsPartialActivation implements the PartialActivation interface by proxying to the internal activation. +func (f *ExecutionFrame) AsPartialActivation() (PartialActivation, bool) { + return AsPartialActivation(f.Activation) +} + +// Unwrap returns the internal activation. +func (f *ExecutionFrame) Unwrap() Activation { + return f.Activation +} + +// IsLocalVariable reports whether the variable name is locally bound in the frame. +func (f *ExecutionFrame) IsLocalVariable(name string) bool { + if holder, ok := f.Activation.(localVariableHolder); ok { + if holder.IsLocalVariable(name) { + return true + } + } + // Search parent scopes + if f.parent != nil { + return f.parent.IsLocalVariable(name) + } + return false +} + +// CheckInterrupt returns whether the evaluation has been interrupted. +func (f *ExecutionFrame) CheckInterrupt() bool { + if f.ctx == nil { + return false + } + if f.ctx.interrupted.Load() { + return true + } + count := f.ctx.interruptCheckCount.Add(1) + if f.ctx.interruptCheckFrequency > 0 && count%uint64(f.ctx.interruptCheckFrequency) == 0 { + select { + case <-f.ctx.interrupt: + f.ctx.interrupted.Store(true) + return true + default: + return false + } + } + return false +} + +// ComputeResult tracks and computes the result of the given asynchronous function. +// +// The first invocation for a given (node id, args) tuple registers the call state and returns an +// Unknown which references the call's unique callID. Subsequent invocations return the cached +// result once the call has completed. Launching background execution is deferred to post-execution +// dispatch via DispatchPendingAsyncCalls. +func (f *ExecutionFrame) ComputeResult(id int64, function, overload string, impl functions.AsyncOp, argVals []ref.Val) ref.Val { + if f.ctx == nil || f.ctx.asyncCalls == nil { + return types.NewErrWithNodeID(id, "asynchronous function calls require concurrent evaluation and cannot be resolved by a synchronous Eval") + } + t := f.ctx.asyncCalls + acs := t.getOrCreate(id, function, overload, argVals, impl, f.ctx.gate) + if res := acs.ResultOrUnknown(); res != nil { + return res + } + return types.NewUnknown(acs.callID, nil) +} + +// DispatchPendingAsyncCalls launches pending asynchronous calls for the specified required call IDs. +func (f *ExecutionFrame) DispatchPendingAsyncCalls(callIDs []int64) { + if f.ctx == nil || f.ctx.asyncCalls == nil { + return + } + t := f.ctx.asyncCalls + for _, callID := range callIDs { + if acs := t.getByID(callID); acs != nil { + t.launch(f.ctx.ctx, acs, f.ctx.observer) + } + } +} + +// ActiveAsyncCalls returns the number of async function calls that have been launched +// but whose completions have not yet been drained. +func (f *ExecutionFrame) ActiveAsyncCalls() int { + if f.ctx == nil || f.ctx.gate == nil { + return 0 + } + return f.ctx.gate.ActiveCalls() +} + +// AsyncCall returns the state of an async call by its callID, or nil if not found. +func (f *ExecutionFrame) AsyncCall(callID int64) AsyncCall { + if f.ctx == nil || f.ctx.asyncCalls == nil { + return nil + } + acs := f.ctx.asyncCalls.getByID(callID) + if acs == nil { + return nil + } + return acs +} + +// SetCompletions configures a channel to receive callIDs when asynchronous evaluations finish. +func (f *ExecutionFrame) SetCompletions(ch chan<- int64) error { + if f.ctx == nil { + return errors.New("asynchronous evaluation options require the execution frame to have a context configured") + } + f.ctx.gate.completions = ch + return nil +} + +// SetAsyncObserver sets the observer for monitoring asynchronous function calls. +func (f *ExecutionFrame) SetAsyncObserver(observer AsyncObserver) error { + if f.ctx == nil { + return errors.New("asynchronous evaluation options require the execution frame to have a context configured") + } + f.ctx.observer = observer + return nil +} + +// SetAsyncMaxConcurrency sets the maximum concurrency for asynchronous function calls. +// +// A non-positive value indicates that concurrency is unbounded. +func (f *ExecutionFrame) SetAsyncMaxConcurrency(n int) error { + if f.ctx == nil { + return errors.New("asynchronous evaluation options require the execution frame to have a context configured") + } + if n > 0 { + f.ctx.gate.semaphore = make(chan struct{}, n) + } else { + f.ctx.gate.semaphore = nil + } + return nil +} + +// frameStack provides a synchronized pool of ExecutionFrames. +var frameStack = &sync.Pool{ + New: func() any { + return &ExecutionFrame{} + }, +} + +// evalContextPool provides a synchronized pool of evalContexts. +var evalContextPool = &sync.Pool{ + New: func() any { + return &evalContext{} + }, +} + +type activationStackPool struct { + sync.Pool +} + +func (pool *activationStackPool) create(parent, child Activation) Activation { + h := pool.Get().(*hierarchicalActivation) + h.child = child + h.parent = parent + h.poolAllocated = true + return h +} + +func (pool *activationStackPool) release(activation Activation) { + h, ok := activation.(*hierarchicalActivation) + if !ok || !h.poolAllocated { + return + } + h.parent = nil + h.child = nil + pool.Pool.Put(h) +} + +func newActivationStackPool() *activationStackPool { + return &activationStackPool{ + Pool: sync.Pool{ + New: func() any { + return &hierarchicalActivation{} + }, + }, + } +} + +type inputActivation struct { + vars map[string]any + lazyVars map[string]any +} + +// ResolveName looks up the value of the input variable name, if found. +// +// Lazy bindings may be supplied within the map-based input in either of the following forms: +// - func() any +// - func() ref.Val +// +// The lazy binding will only be invoked once per evaluation. +// +// Values which are not represented as ref.Val types on input may be adapted to a ref.Val using +// the types.Adapter configured in the environment. +func (a *inputActivation) ResolveName(name string) (any, bool) { + v, found := a.vars[name] + if !found { + return nil, false + } + switch obj := v.(type) { + case func() ref.Val: + if resolved, found := a.lazyVars[name]; found { + return resolved, true + } + lazy := obj() + a.lazyVars[name] = lazy + return lazy, true + case func() any: + if resolved, found := a.lazyVars[name]; found { + return resolved, true + } + lazy := obj() + a.lazyVars[name] = lazy + return lazy, true + default: + return obj, true + } +} + +// Parent implements the Activation interface +func (a *inputActivation) Parent() Activation { + return nil +} + +func newActivationInputPool() *activationInputPool { + return &activationInputPool{ + Pool: sync.Pool{ + New: func() any { + return &inputActivation{ + lazyVars: make(map[string]any), + } + }, + }, + } +} + +type activationInputPool struct { + sync.Pool +} + +// create initializes a pooled Activation object with the map input. +func (p *activationInputPool) create(vars map[string]any) *inputActivation { + a := p.Pool.Get().(*inputActivation) + a.vars = vars + return a +} + +func (p *activationInputPool) release(value any) { + a := value.(*inputActivation) + for k := range a.lazyVars { + delete(a.lazyVars, k) + } + a.vars = nil + p.Pool.Put(a) +} + +var ( + activationStack = newActivationStackPool() + activationInput = newActivationInputPool() +) diff --git a/vendor/github.com/google/cel-go/interpreter/interpretable.go b/vendor/github.com/google/cel-go/interpreter/interpretable.go index 96b5a8ffc..906c4f805 100644 --- a/vendor/github.com/google/cel-go/interpreter/interpretable.go +++ b/vendor/github.com/google/cel-go/interpreter/interpretable.go @@ -26,20 +26,53 @@ import ( "github.com/google/cel-go/common/types/traits" ) -// Interpretable can accept a given Activation and produce a value along with -// an accompanying EvalState which can be used to inspect whether additional -// data might be necessary to complete the evaluation. +// Interpretable evaluates an Activation and produces a value. type Interpretable interface { // ID value corresponding to the expression node. ID() int64 - // Eval an Activation to produce an output. + // Eval evaluates an Activation and produces an output. Eval(activation Activation) ref.Val } +// InterpretableV2 evaluates an ExecutionFrame and produces a value. +// +// The ExecutionFrame should not be stored and should always be passed as the first +// argument to any function as it behaves like Golang's context.Context. +type InterpretableV2 interface { + Interpretable + + // Exec evaluates the expression within the given ExecutionFrame. + Exec(frame *ExecutionFrame) ref.Val +} + +// adaptToV2 adapts a V1 Interpretable implementation to the V2 interface. +// +// This adapter is used to bridge the legacy Interpretable interface to the +// modern InterpretableV2 interface, providing a shim that allows the use of +// both interfaces in the same system. +func adaptToV2(i Interpretable) InterpretableV2 { + switch v := i.(type) { + case InterpretableV2: + return v + default: + return &v1Adapter{Interpretable: v} + } +} + +// v1Adapter handles bridging a V1 Interpretable implementation to the V2 interface. +type v1Adapter struct { + Interpretable +} + +// Exec implements the InterpretableV2 interface method. +func (a *v1Adapter) Exec(f *ExecutionFrame) ref.Val { + return a.Eval(f) +} + // InterpretableConst interface for tracking whether the Interpretable is a constant value. type InterpretableConst interface { - Interpretable + InterpretableV2 // Value returns the constant value of the instruction. Value() ref.Val @@ -47,7 +80,7 @@ type InterpretableConst interface { // InterpretableAttribute interface for tracking whether the Interpretable is an attribute. type InterpretableAttribute interface { - Interpretable + InterpretableV2 // Attr returns the Attribute value. Attr() Attribute @@ -81,7 +114,7 @@ type InterpretableAttribute interface { // InterpretableCall interface for inspecting Interpretable instructions related to function calls. type InterpretableCall interface { - Interpretable + InterpretableV2 // Function returns the function name as it appears in text or mangled operator name as it // appears in the operators.go file. @@ -94,16 +127,16 @@ type InterpretableCall interface { // Args returns the normalized arguments to the function overload. // For receiver-style functions, the receiver target is arg 0. - Args() []Interpretable + Args() []InterpretableV2 } // InterpretableConstructor interface for inspecting Interpretable instructions that initialize a list, map // or struct. type InterpretableConstructor interface { - Interpretable + InterpretableV2 // InitVals returns all the list elements, map key and values or struct field values. - InitVals() []Interpretable + InitVals() []InterpretableV2 // Type returns the type constructed. Type() ref.Type @@ -112,18 +145,23 @@ type InterpretableConstructor interface { // ObservableInterpretable is an Interpretable which supports stateful observation, such as tracing // or cost-tracking. type ObservableInterpretable struct { - Interpretable + InterpretableV2 observers []StatefulObserver } // ID implements the Interpretable method to get the expression id associated with the step. func (oi *ObservableInterpretable) ID() int64 { - return oi.Interpretable.ID() + return oi.InterpretableV2.ID() +} + +// Exec implements the InterpretableV2 interface method. +func (oi *ObservableInterpretable) Exec(frame *ExecutionFrame) ref.Val { + return oi.ObserveExec(frame, func(any) {}) } // Eval proxies to the ObserveEval method while invoking a no-op callback to report the observations. func (oi *ObservableInterpretable) Eval(vars Activation) ref.Val { - return oi.ObserveEval(vars, func(any) {}) + return oi.ObserveExec(AsFrame(vars), func(any) {}) } // ObserveEval evaluates an interpretable and performs per-evaluation state-tracking. @@ -131,25 +169,65 @@ func (oi *ObservableInterpretable) Eval(vars Activation) ref.Val { // This method is concurrency safe and the expectation is that the observer function will use // a switch statement to determine the type of the state which has been reported back from the call. func (oi *ObservableInterpretable) ObserveEval(vars Activation, observer func(any)) ref.Val { - var err error + return oi.ObserveExec(AsFrame(vars), observer) +} + +// ObserveExec evaluates an interpretable and performs per-evaluation state-tracking. +// +// This method is concurrency safe and the expectation is that the observer function will use +// a switch statement to determine the type of the state which has been reported back from the call. +func (oi *ObservableInterpretable) ObserveExec(frame *ExecutionFrame, observer func(any)) ref.Val { // Initialize the state needed for the observers to function. for _, obs := range oi.observers { - vars, err = obs.InitState(vars) + state, err := obs.InitState(frame) if err != nil { return types.WrapErr(err) } // Provide an initial reference to the state to ensure state is available // even in cases of interrupting errors generated during evaluation. - observer(obs.GetState(vars)) + observer(state) } - result := oi.Interpretable.Eval(vars) + result := oi.InterpretableV2.Exec(frame) // Get the state which needs to be reported back as having been observed. for _, obs := range oi.observers { - observer(obs.GetState(vars)) + observer(obs.GetState(frame)) } return result } +// AsFrame promotes an Activation to an ExecutionFrame. +func AsFrame(a Activation) *ExecutionFrame { + if f, ok := a.(*ExecutionFrame); ok { + return f + } + frame := &ExecutionFrame{Activation: a} + // Walk the activation hierarchy to find a parent ExecutionFrame and inherit + // its shared context. + if parent := findFrame(a); parent != nil { + frame.ctx = parent.ctx + } + return frame +} + +// findFrame walks the activation hierarchy via Unwrap and Parent to locate an +// existing ExecutionFrame, if one exists. +func findFrame(a Activation) *ExecutionFrame { + if wrapper, ok := a.(activationWrapper); ok { + unwrapped := wrapper.Unwrap() + if f, ok := unwrapped.(*ExecutionFrame); ok { + return f + } + return findFrame(unwrapped) + } + if p := a.Parent(); p != nil { + if f, ok := p.(*ExecutionFrame); ok { + return f + } + return findFrame(p) + } + return nil +} + // Core Interpretable implementations used during the program planning phase. type evalTestOnly struct { @@ -162,9 +240,9 @@ func (test *evalTestOnly) ID() int64 { return test.id } -// Eval implements the Interpretable interface method. -func (test *evalTestOnly) Eval(ctx Activation) ref.Val { - val, err := test.Resolve(ctx) +// Exec implements the InterpretableV2 interface method. +func (test *evalTestOnly) Exec(frame *ExecutionFrame) ref.Val { + val, err := test.Resolve(frame) // Return an error if the resolve step fails if err != nil { return types.LabelErrNode(test.id, types.WrapErr(err)) @@ -175,6 +253,11 @@ func (test *evalTestOnly) Eval(ctx Activation) ref.Val { return test.Adapter().NativeToValue(val) } +// Eval implements the Interpretable interface method. +func (test *evalTestOnly) Eval(ctx Activation) ref.Val { + return test.Exec(AsFrame(ctx)) +} + // AddQualifier appends a qualifier that will always and only perform a presence test. func (test *evalTestOnly) AddQualifier(q Qualifier) (Attribute, error) { cq, ok := q.(ConstantQualifier) @@ -194,7 +277,7 @@ func (q *testOnlyQualifier) Qualify(vars Activation, obj any) (any, error) { if err != nil { return nil, err } - if unk, isUnk := out.(types.Unknown); isUnk { + if unk, isUnk := out.(*types.Unknown); isUnk { return unk, nil } return present, nil @@ -230,6 +313,11 @@ func (cons *evalConst) ID() int64 { return cons.id } +// Exec implements the InterpretableV2 interface method. +func (cons *evalConst) Exec(frame *ExecutionFrame) ref.Val { + return cons.val +} + // Eval implements the Interpretable interface method. func (cons *evalConst) Eval(ctx Activation) ref.Val { return cons.val @@ -242,7 +330,7 @@ func (cons *evalConst) Value() ref.Val { type evalOr struct { id int64 - terms []Interpretable + terms []InterpretableV2 } // ID implements the Interpretable interface method. @@ -250,12 +338,12 @@ func (or *evalOr) ID() int64 { return or.id } -// Eval implements the Interpretable interface method. -func (or *evalOr) Eval(ctx Activation) ref.Val { +// Exec implements the InterpretableV2 interface method. +func (or *evalOr) Exec(frame *ExecutionFrame) ref.Val { var err ref.Val = nil var unk *types.Unknown for _, term := range or.terms { - val := term.Eval(ctx) + val := term.Exec(frame) boolVal, ok := val.(types.Bool) // short-circuit on true. if ok && boolVal == types.True { @@ -283,9 +371,14 @@ func (or *evalOr) Eval(ctx Activation) ref.Val { return types.False } +// Eval implements the Interpretable interface method. +func (or *evalOr) Eval(ctx Activation) ref.Val { + return or.Exec(AsFrame(ctx)) +} + type evalAnd struct { id int64 - terms []Interpretable + terms []InterpretableV2 } // ID implements the Interpretable interface method. @@ -293,12 +386,12 @@ func (and *evalAnd) ID() int64 { return and.id } -// Eval implements the Interpretable interface method. -func (and *evalAnd) Eval(ctx Activation) ref.Val { +// Exec implements the InterpretableV2 interface method. +func (and *evalAnd) Exec(frame *ExecutionFrame) ref.Val { var err ref.Val = nil var unk *types.Unknown for _, term := range and.terms { - val := term.Eval(ctx) + val := term.Exec(frame) boolVal, ok := val.(types.Bool) // short-circuit on false. if ok && boolVal == types.False { @@ -326,10 +419,15 @@ func (and *evalAnd) Eval(ctx Activation) ref.Val { return types.True } +// Eval implements the Interpretable interface method. +func (and *evalAnd) Eval(ctx Activation) ref.Val { + return and.Exec(AsFrame(ctx)) +} + type evalEq struct { id int64 - lhs Interpretable - rhs Interpretable + lhs InterpretableV2 + rhs InterpretableV2 } // ID implements the Interpretable interface method. @@ -337,19 +435,30 @@ func (eq *evalEq) ID() int64 { return eq.id } -// Eval implements the Interpretable interface method. -func (eq *evalEq) Eval(ctx Activation) ref.Val { - lVal := eq.lhs.Eval(ctx) - rVal := eq.rhs.Eval(ctx) - if types.IsUnknownOrError(lVal) { +// Exec implements the InterpretableV2 interface method. +func (eq *evalEq) Exec(frame *ExecutionFrame) ref.Val { + lVal := eq.lhs.Exec(frame) + if types.IsError(lVal) { return lVal } - if types.IsUnknownOrError(rVal) { + rVal := eq.rhs.Exec(frame) + if types.IsError(rVal) { return rVal } + var unk *types.Unknown + unk, _ = types.MaybeMergeUnknowns(lVal, unk) + unk, _ = types.MaybeMergeUnknowns(rVal, unk) + if unk != nil { + return unk + } return types.Equal(lVal, rVal) } +// Eval implements the Interpretable interface method. +func (eq *evalEq) Eval(ctx Activation) ref.Val { + return eq.Exec(AsFrame(ctx)) +} + // Function implements the InterpretableCall interface method. func (*evalEq) Function() string { return operators.Equals @@ -361,14 +470,14 @@ func (*evalEq) OverloadID() string { } // Args implements the InterpretableCall interface method. -func (eq *evalEq) Args() []Interpretable { - return []Interpretable{eq.lhs, eq.rhs} +func (eq *evalEq) Args() []InterpretableV2 { + return []InterpretableV2{eq.lhs, eq.rhs} } type evalNe struct { id int64 - lhs Interpretable - rhs Interpretable + lhs InterpretableV2 + rhs InterpretableV2 } // ID implements the Interpretable interface method. @@ -376,19 +485,30 @@ func (ne *evalNe) ID() int64 { return ne.id } -// Eval implements the Interpretable interface method. -func (ne *evalNe) Eval(ctx Activation) ref.Val { - lVal := ne.lhs.Eval(ctx) - rVal := ne.rhs.Eval(ctx) - if types.IsUnknownOrError(lVal) { +// Exec implements the InterpretableV2 interface method. +func (ne *evalNe) Exec(frame *ExecutionFrame) ref.Val { + lVal := ne.lhs.Exec(frame) + if types.IsError(lVal) { return lVal } - if types.IsUnknownOrError(rVal) { + rVal := ne.rhs.Exec(frame) + if types.IsError(rVal) { return rVal } + var unk *types.Unknown + unk, _ = types.MaybeMergeUnknowns(lVal, unk) + unk, _ = types.MaybeMergeUnknowns(rVal, unk) + if unk != nil { + return unk + } return types.Bool(types.Equal(lVal, rVal) != types.True) } +// Eval implements the Interpretable interface method. +func (ne *evalNe) Eval(ctx Activation) ref.Val { + return ne.Exec(AsFrame(ctx)) +} + // Function implements the InterpretableCall interface method. func (*evalNe) Function() string { return operators.NotEquals @@ -400,8 +520,8 @@ func (*evalNe) OverloadID() string { } // Args implements the InterpretableCall interface method. -func (ne *evalNe) Args() []Interpretable { - return []Interpretable{ne.lhs, ne.rhs} +func (ne *evalNe) Args() []InterpretableV2 { + return []InterpretableV2{ne.lhs, ne.rhs} } type evalZeroArity struct { @@ -416,9 +536,14 @@ func (zero *evalZeroArity) ID() int64 { return zero.id } +// Exec implements the InterpretableV2 interface method. +func (zero *evalZeroArity) Exec(frame *ExecutionFrame) ref.Val { + return types.LabelErrNode(zero.id, zero.impl()) +} + // Eval implements the Interpretable interface method. func (zero *evalZeroArity) Eval(ctx Activation) ref.Val { - return types.LabelErrNode(zero.id, zero.impl()) + return zero.Exec(AsFrame(ctx)) } // Function implements the InterpretableCall interface method. @@ -432,15 +557,15 @@ func (zero *evalZeroArity) OverloadID() string { } // Args returns the argument to the unary function. -func (zero *evalZeroArity) Args() []Interpretable { - return []Interpretable{} +func (zero *evalZeroArity) Args() []InterpretableV2 { + return []InterpretableV2{} } type evalUnary struct { id int64 function string overload string - arg Interpretable + arg InterpretableV2 trait int impl functions.UnaryOp nonStrict bool @@ -451,9 +576,9 @@ func (un *evalUnary) ID() int64 { return un.id } -// Eval implements the Interpretable interface method. -func (un *evalUnary) Eval(ctx Activation) ref.Val { - argVal := un.arg.Eval(ctx) +// Exec implements the InterpretableV2 interface method. +func (un *evalUnary) Exec(frame *ExecutionFrame) ref.Val { + argVal := un.arg.Exec(frame) // Early return if the argument to the function is unknown or error. strict := !un.nonStrict if strict && types.IsUnknownOrError(argVal) { @@ -472,6 +597,11 @@ func (un *evalUnary) Eval(ctx Activation) ref.Val { return types.NewErrWithNodeID(un.id, "no such overload: %s", un.function) } +// Eval implements the Interpretable interface method. +func (un *evalUnary) Eval(ctx Activation) ref.Val { + return un.Exec(AsFrame(ctx)) +} + // Function implements the InterpretableCall interface method. func (un *evalUnary) Function() string { return un.function @@ -483,16 +613,16 @@ func (un *evalUnary) OverloadID() string { } // Args returns the argument to the unary function. -func (un *evalUnary) Args() []Interpretable { - return []Interpretable{un.arg} +func (un *evalUnary) Args() []InterpretableV2 { + return []InterpretableV2{un.arg} } type evalBinary struct { id int64 function string overload string - lhs Interpretable - rhs Interpretable + lhs InterpretableV2 + rhs InterpretableV2 trait int impl functions.BinaryOp nonStrict bool @@ -503,18 +633,23 @@ func (bin *evalBinary) ID() int64 { return bin.id } -// Eval implements the Interpretable interface method. -func (bin *evalBinary) Eval(ctx Activation) ref.Val { - lVal := bin.lhs.Eval(ctx) - rVal := bin.rhs.Eval(ctx) - // Early return if any argument to the function is unknown or error. +// Exec implements the InterpretableV2 interface method. +func (bin *evalBinary) Exec(frame *ExecutionFrame) ref.Val { + lVal := bin.lhs.Exec(frame) strict := !bin.nonStrict + if strict && types.IsError(lVal) { + return lVal + } + rVal := bin.rhs.Exec(frame) + if strict && types.IsError(rVal) { + return rVal + } if strict { - if types.IsUnknownOrError(lVal) { - return lVal - } - if types.IsUnknownOrError(rVal) { - return rVal + var unk *types.Unknown + unk, _ = types.MaybeMergeUnknowns(lVal, unk) + unk, _ = types.MaybeMergeUnknowns(rVal, unk) + if unk != nil { + return unk } } // If the implementation is bound and the argument value has the right traits required to @@ -530,6 +665,11 @@ func (bin *evalBinary) Eval(ctx Activation) ref.Val { return types.NewErrWithNodeID(bin.id, "no such overload: %s", bin.function) } +// Eval implements the Interpretable interface method. +func (bin *evalBinary) Eval(ctx Activation) ref.Val { + return bin.Exec(AsFrame(ctx)) +} + // Function implements the InterpretableCall interface method. func (bin *evalBinary) Function() string { return bin.function @@ -541,22 +681,22 @@ func (bin *evalBinary) OverloadID() string { } // Args returns the argument to the unary function. -func (bin *evalBinary) Args() []Interpretable { - return []Interpretable{bin.lhs, bin.rhs} +func (bin *evalBinary) Args() []InterpretableV2 { + return []InterpretableV2{bin.lhs, bin.rhs} } type evalVarArgs struct { id int64 function string overload string - args []Interpretable + args []InterpretableV2 trait int impl functions.FunctionOp nonStrict bool } // NewCall creates a new call Interpretable. -func NewCall(id int64, function, overload string, args []Interpretable, impl functions.FunctionOp) InterpretableCall { +func NewCall(id int64, function, overload string, args []InterpretableV2, impl functions.FunctionOp) InterpretableCall { return &evalVarArgs{ id: id, function: function, @@ -571,17 +711,23 @@ func (fn *evalVarArgs) ID() int64 { return fn.id } -// Eval implements the Interpretable interface method. -func (fn *evalVarArgs) Eval(ctx Activation) ref.Val { +// Exec implements the InterpretableV2 interface method. +func (fn *evalVarArgs) Exec(frame *ExecutionFrame) ref.Val { argVals := make([]ref.Val, len(fn.args)) - // Early return if any argument to the function is unknown or error. strict := !fn.nonStrict + var unk *types.Unknown for i, arg := range fn.args { - argVals[i] = arg.Eval(ctx) - if strict && types.IsUnknownOrError(argVals[i]) { - return argVals[i] + argVals[i] = arg.Exec(frame) + if strict { + if types.IsError(argVals[i]) { + return argVals[i] + } + unk, _ = types.MaybeMergeUnknowns(argVals[i], unk) } } + if strict && unk != nil { + return unk + } // If the implementation is bound and the argument value has the right traits required to // invoke it, then call the implementation. arg0 := argVals[0] @@ -596,6 +742,11 @@ func (fn *evalVarArgs) Eval(ctx Activation) ref.Val { return types.NewErrWithNodeID(fn.id, "no such overload: %s %d", fn.function, fn.id) } +// Eval implements the Interpretable interface method. +func (fn *evalVarArgs) Eval(ctx Activation) ref.Val { + return fn.Exec(AsFrame(ctx)) +} + // Function implements the InterpretableCall interface method. func (fn *evalVarArgs) Function() string { return fn.function @@ -607,13 +758,13 @@ func (fn *evalVarArgs) OverloadID() string { } // Args returns the argument to the unary function. -func (fn *evalVarArgs) Args() []Interpretable { +func (fn *evalVarArgs) Args() []InterpretableV2 { return fn.args } type evalList struct { id int64 - elems []Interpretable + elems []InterpretableV2 optionals []bool hasOptionals bool adapter types.Adapter @@ -624,31 +775,44 @@ func (l *evalList) ID() int64 { return l.id } -// Eval implements the Interpretable interface method. -func (l *evalList) Eval(ctx Activation) ref.Val { +// Exec implements the InterpretableV2 interface method. +func (l *evalList) Exec(frame *ExecutionFrame) ref.Val { elemVals := make([]ref.Val, 0, len(l.elems)) - // If any argument is unknown or error early terminate. + var unk *types.Unknown for i, elem := range l.elems { - elemVal := elem.Eval(ctx) - if types.IsUnknownOrError(elemVal) { + elemVal := elem.Exec(frame) + if types.IsError(elemVal) { return elemVal } + unk, _ = types.MaybeMergeUnknowns(elemVal, unk) if l.hasOptionals && l.optionals[i] { - optVal, ok := elemVal.(*types.Optional) - if !ok { - return types.LabelErrNode(l.id, invalidOptionalElementInit(elemVal)) - } - if !optVal.HasValue() { - continue + if types.IsUnknown(elemVal) { + // skip optional checks for unknown values as they aren't fully resolved yet. + } else { + optVal, ok := elemVal.(*types.Optional) + if !ok { + return types.LabelErrNode(l.id, invalidOptionalElementInit(elemVal)) + } + if !optVal.HasValue() { + continue + } + elemVal = optVal.GetValue() } - elemVal = optVal.GetValue() } elemVals = append(elemVals, elemVal) } - return l.adapter.NativeToValue(elemVals) + if unk != nil { + return unk + } + return types.NewRefValList(l.adapter, elemVals) } -func (l *evalList) InitVals() []Interpretable { +// Eval implements the Interpretable interface method. +func (l *evalList) Eval(ctx Activation) ref.Val { + return l.Exec(AsFrame(ctx)) +} + +func (l *evalList) InitVals() []InterpretableV2 { return l.elems } @@ -658,8 +822,8 @@ func (l *evalList) Type() ref.Type { type evalMap struct { id int64 - keys []Interpretable - vals []Interpretable + keys []InterpretableV2 + vals []InterpretableV2 optionals []bool hasOptionals bool adapter types.Adapter @@ -670,20 +834,24 @@ func (m *evalMap) ID() int64 { return m.id } -// Eval implements the Interpretable interface method. -func (m *evalMap) Eval(ctx Activation) ref.Val { - entries := make(map[ref.Val]ref.Val) - // If any argument is unknown or error early terminate. +// Exec implements the InterpretableV2 interface method. +func (m *evalMap) Exec(frame *ExecutionFrame) ref.Val { + entries := make(map[ref.Val]ref.Val, len(m.keys)) + var unk *types.Unknown for i, key := range m.keys { - keyVal := key.Eval(ctx) - if types.IsUnknownOrError(keyVal) { + keyVal := key.Exec(frame) + if types.IsError(keyVal) { return keyVal } - valVal := m.vals[i].Eval(ctx) - if types.IsUnknownOrError(valVal) { + unk, _ = types.MaybeMergeUnknowns(keyVal, unk) + + valVal := m.vals[i].Exec(frame) + if types.IsError(valVal) { return valVal } - if m.hasOptionals && m.optionals[i] { + unk, _ = types.MaybeMergeUnknowns(valVal, unk) + + if m.hasOptionals && m.optionals[i] && !types.IsUnknown(valVal) { optVal, ok := valVal.(*types.Optional) if !ok { return types.LabelErrNode(m.id, invalidOptionalEntryInit(keyVal, valVal)) @@ -696,14 +864,22 @@ func (m *evalMap) Eval(ctx Activation) ref.Val { } entries[keyVal] = valVal } - return m.adapter.NativeToValue(entries) + if unk != nil { + return unk + } + return types.NewRefValMap(m.adapter, entries) +} + +// Eval implements the Interpretable interface method. +func (m *evalMap) Eval(ctx Activation) ref.Val { + return m.Exec(AsFrame(ctx)) } -func (m *evalMap) InitVals() []Interpretable { +func (m *evalMap) InitVals() []InterpretableV2 { if len(m.keys) != len(m.vals) { return nil } - result := make([]Interpretable, len(m.keys)+len(m.vals)) + result := make([]InterpretableV2, len(m.keys)+len(m.vals)) idx := 0 for i, k := range m.keys { v := m.vals[i] @@ -723,7 +899,7 @@ type evalObj struct { id int64 typeName string fields []string - vals []Interpretable + vals []InterpretableV2 optionals []bool hasOptionals bool provider types.Provider @@ -734,16 +910,17 @@ func (o *evalObj) ID() int64 { return o.id } -// Eval implements the Interpretable interface method. -func (o *evalObj) Eval(ctx Activation) ref.Val { - fieldVals := make(map[string]ref.Val) - // If any argument is unknown or error early terminate. +// Exec implements the InterpretableV2 interface method. +func (o *evalObj) Exec(frame *ExecutionFrame) ref.Val { + fieldVals := make(map[string]ref.Val, len(o.fields)) + var unk *types.Unknown for i, field := range o.fields { - val := o.vals[i].Eval(ctx) - if types.IsUnknownOrError(val) { + val := o.vals[i].Exec(frame) + if types.IsError(val) { return val } - if o.hasOptionals && o.optionals[i] { + unk, _ = types.MaybeMergeUnknowns(val, unk) + if o.hasOptionals && o.optionals[i] && !types.IsUnknown(val) { optVal, ok := val.(*types.Optional) if !ok { return types.LabelErrNode(o.id, invalidOptionalEntryInit(field, val)) @@ -756,11 +933,19 @@ func (o *evalObj) Eval(ctx Activation) ref.Val { } fieldVals[field] = val } + if unk != nil { + return unk + } return types.LabelErrNode(o.id, o.provider.NewValue(o.typeName, fieldVals)) } +// Eval implements the Interpretable interface method. +func (o *evalObj) Eval(ctx Activation) ref.Val { + return o.Exec(AsFrame(ctx)) +} + // InitVals implements the InterpretableConstructor interface method. -func (o *evalObj) InitVals() []Interpretable { +func (o *evalObj) InitVals() []InterpretableV2 { return o.vals } @@ -774,11 +959,11 @@ type evalFold struct { accuVar string iterVar string iterVar2 string - iterRange Interpretable - accu Interpretable - cond Interpretable - step Interpretable - result Interpretable + iterRange InterpretableV2 + accu InterpretableV2 + cond InterpretableV2 + step InterpretableV2 + result InterpretableV2 adapter types.Adapter // note an exhaustive fold will ensure that all branches are evaluated @@ -793,13 +978,13 @@ func (fold *evalFold) ID() int64 { return fold.id } -// Eval implements the Interpretable interface method. -func (fold *evalFold) Eval(ctx Activation) ref.Val { +// Exec implements the InterpretableV2 interface method. +func (fold *evalFold) Exec(frame *ExecutionFrame) ref.Val { // Initialize the folder interface - f := newFolder(fold, ctx) + f := newFolder(fold, frame) defer releaseFolder(f) - foldRange := fold.iterRange.Eval(ctx) + foldRange := fold.iterRange.Exec(frame) if types.IsUnknownOrError(foldRange) { return foldRange } @@ -824,14 +1009,19 @@ func (fold *evalFold) Eval(ctx Activation) ref.Val { return f.foldIterable(iterable) } +// Eval implements the Interpretable interface method. +func (fold *evalFold) Eval(ctx Activation) ref.Val { + return fold.Exec(AsFrame(ctx)) +} + // Optional Interpretable implementations that specialize, subsume, or extend the core evaluation // plan via decorators. // evalSetMembership is an Interpretable implementation which tests whether an input value // exists within the set of map keys used to model a set. type evalSetMembership struct { - inst Interpretable - arg Interpretable + inst InterpretableV2 + arg InterpretableV2 valueSet map[ref.Val]ref.Val } @@ -840,9 +1030,9 @@ func (e *evalSetMembership) ID() int64 { return e.inst.ID() } -// Eval implements the Interpretable interface method. -func (e *evalSetMembership) Eval(ctx Activation) ref.Val { - val := e.arg.Eval(ctx) +// Exec implements the InterpretableV2 interface method. +func (e *evalSetMembership) Exec(frame *ExecutionFrame) ref.Val { + val := e.arg.Exec(frame) if types.IsUnknownOrError(val) { return val } @@ -852,18 +1042,28 @@ func (e *evalSetMembership) Eval(ctx Activation) ref.Val { return types.False } +// Eval implements the Interpretable interface method. +func (e *evalSetMembership) Eval(ctx Activation) ref.Val { + return e.Exec(AsFrame(ctx)) +} + // evalWatch is an Interpretable implementation that wraps the execution of a given // expression so that it may observe the computed value and send it to an observer. type evalWatch struct { - Interpretable + InterpretableV2 observer EvalObserver } +// Exec implements the InterpretableV2 interface method. +func (e *evalWatch) Exec(frame *ExecutionFrame) ref.Val { + val := e.InterpretableV2.Exec(frame) + e.observer(frame, e.ID(), e.InterpretableV2, val) + return val +} + // Eval implements the Interpretable interface method. func (e *evalWatch) Eval(vars Activation) ref.Val { - val := e.Interpretable.Eval(vars) - e.observer(vars, e.ID(), e.Interpretable, val) - return val + return e.Exec(AsFrame(vars)) } // evalWatchAttr describes a watcher of an InterpretableAttribute Interpretable. @@ -918,11 +1118,16 @@ func (e *evalWatchAttr) AddQualifier(q Qualifier) (Attribute, error) { return e, err } +// Exec implements the InterpretableV2 interface method. +func (e *evalWatchAttr) Exec(frame *ExecutionFrame) ref.Val { + val := e.InterpretableAttribute.Exec(frame) + e.observer(frame, e.ID(), e.InterpretableAttribute, val) + return val +} + // Eval implements the Interpretable interface method. func (e *evalWatchAttr) Eval(vars Activation) ref.Val { - val := e.InterpretableAttribute.Eval(vars) - e.observer(vars, e.ID(), e.InterpretableAttribute, val) - return val + return e.Exec(AsFrame(vars)) } // evalWatchConstQual observes the qualification of an object using a constant boolean, int, @@ -1049,17 +1254,22 @@ type evalWatchConst struct { observer EvalObserver } -// Eval implements the Interpretable interface method. -func (e *evalWatchConst) Eval(vars Activation) ref.Val { +// Exec implements the InterpretableV2 interface method. +func (e *evalWatchConst) Exec(frame *ExecutionFrame) ref.Val { val := e.Value() - e.observer(vars, e.ID(), e.InterpretableConst, val) + e.observer(frame, e.ID(), e.InterpretableConst, val) return val } +// Eval implements the Interpretable interface method. +func (e *evalWatchConst) Eval(vars Activation) ref.Val { + return e.Exec(AsFrame(vars)) +} + // evalExhaustiveOr is just like evalOr, but does not short-circuit argument evaluation. type evalExhaustiveOr struct { id int64 - terms []Interpretable + terms []InterpretableV2 } // ID implements the Interpretable interface method. @@ -1067,13 +1277,13 @@ func (or *evalExhaustiveOr) ID() int64 { return or.id } -// Eval implements the Interpretable interface method. -func (or *evalExhaustiveOr) Eval(ctx Activation) ref.Val { +// Exec implements the InterpretableV2 interface method. +func (or *evalExhaustiveOr) Exec(frame *ExecutionFrame) ref.Val { var err ref.Val = nil var unk *types.Unknown isTrue := false for _, term := range or.terms { - val := term.Eval(ctx) + val := term.Exec(frame) boolVal, ok := val.(types.Bool) // flag the result as true if ok && boolVal == types.True { @@ -1103,10 +1313,15 @@ func (or *evalExhaustiveOr) Eval(ctx Activation) ref.Val { return types.False } +// Eval implements the Interpretable interface method. +func (or *evalExhaustiveOr) Eval(ctx Activation) ref.Val { + return or.Exec(AsFrame(ctx)) +} + // evalExhaustiveAnd is just like evalAnd, but does not short-circuit argument evaluation. type evalExhaustiveAnd struct { id int64 - terms []Interpretable + terms []InterpretableV2 } // ID implements the Interpretable interface method. @@ -1114,13 +1329,13 @@ func (and *evalExhaustiveAnd) ID() int64 { return and.id } -// Eval implements the Interpretable interface method. -func (and *evalExhaustiveAnd) Eval(ctx Activation) ref.Val { +// Exec implements the InterpretableV2 interface method. +func (and *evalExhaustiveAnd) Exec(frame *ExecutionFrame) ref.Val { var err ref.Val = nil var unk *types.Unknown isFalse := false for _, term := range and.terms { - val := term.Eval(ctx) + val := term.Exec(frame) boolVal, ok := val.(types.Bool) // short-circuit on false. if ok && boolVal == types.False { @@ -1150,6 +1365,11 @@ func (and *evalExhaustiveAnd) Eval(ctx Activation) ref.Val { return types.True } +// Eval implements the Interpretable interface method. +func (and *evalExhaustiveAnd) Eval(ctx Activation) ref.Val { + return and.Exec(AsFrame(ctx)) +} + // evalExhaustiveConditional is like evalConditional, but does not short-circuit argument // evaluation. type evalExhaustiveConditional struct { @@ -1163,11 +1383,11 @@ func (cond *evalExhaustiveConditional) ID() int64 { return cond.id } -// Eval implements the Interpretable interface method. -func (cond *evalExhaustiveConditional) Eval(ctx Activation) ref.Val { - cVal := cond.attr.expr.Eval(ctx) - tVal, tErr := cond.attr.truthy.Resolve(ctx) - fVal, fErr := cond.attr.falsy.Resolve(ctx) +// Exec implements the InterpretableV2 interface method. +func (cond *evalExhaustiveConditional) Exec(frame *ExecutionFrame) ref.Val { + cVal := cond.attr.expr.Exec(frame) + tVal, tErr := cond.attr.truthy.Resolve(frame) + fVal, fErr := cond.attr.falsy.Resolve(frame) cBool, ok := cVal.(types.Bool) if !ok { return types.ValOrErr(cVal, "no such overload") @@ -1184,6 +1404,11 @@ func (cond *evalExhaustiveConditional) Eval(ctx Activation) ref.Val { return cond.adapter.NativeToValue(fVal) } +// Eval implements the Interpretable interface method. +func (cond *evalExhaustiveConditional) Eval(ctx Activation) ref.Val { + return cond.Exec(AsFrame(ctx)) +} + // evalAttr evaluates an Attribute value. type evalAttr struct { adapter types.Adapter @@ -1215,15 +1440,20 @@ func (a *evalAttr) Adapter() types.Adapter { return a.adapter } -// Eval implements the Interpretable interface method. -func (a *evalAttr) Eval(ctx Activation) ref.Val { - v, err := a.attr.Resolve(ctx) +// Exec implements the InterpretableV2 interface method. +func (a *evalAttr) Exec(frame *ExecutionFrame) ref.Val { + v, err := a.attr.Resolve(frame) if err != nil { return types.LabelErrNode(a.ID(), types.WrapErr(err)) } return a.adapter.NativeToValue(v) } +// Eval implements the Interpretable interface method. +func (a *evalAttr) Eval(ctx Activation) ref.Val { + return a.Exec(AsFrame(ctx)) +} + // Qualify proxies to the Attribute's Qualify method. func (a *evalAttr) Qualify(vars Activation, obj any) (any, error) { return a.attr.Qualify(vars, obj) @@ -1249,7 +1479,7 @@ type evalWatchConstructor struct { } // InitVals implements the InterpretableConstructor InitVals function. -func (c *evalWatchConstructor) InitVals() []Interpretable { +func (c *evalWatchConstructor) InitVals() []InterpretableV2 { return c.constructor.InitVals() } @@ -1263,11 +1493,16 @@ func (c *evalWatchConstructor) ID() int64 { return c.constructor.ID() } +// Exec implements the InterpretableV2 interface method. +func (c *evalWatchConstructor) Exec(frame *ExecutionFrame) ref.Val { + val := c.constructor.Exec(frame) + c.observer(frame, c.ID(), c.constructor, val) + return val +} + // Eval implements the Interpretable Eval function. func (c *evalWatchConstructor) Eval(vars Activation) ref.Val { - val := c.constructor.Eval(vars) - c.observer(vars, c.ID(), c.constructor, val) - return val + return c.Exec(AsFrame(vars)) } func invalidOptionalEntryInit(field any, value ref.Val) ref.Val { @@ -1279,10 +1514,10 @@ func invalidOptionalElementInit(value ref.Val) ref.Val { } // newFolder creates or initializes a pooled folder instance. -func newFolder(eval *evalFold, ctx Activation) *folder { +func newFolder(eval *evalFold, frame *ExecutionFrame) *folder { f := folderPool.Get().(*folder) f.evalFold = eval - f.activation = ctx + f.frame = frame.Push(f) return f } @@ -1303,7 +1538,7 @@ func releaseFolder(f *folder) { // cel.bind or cel.@block. type folder struct { *evalFold - activation Activation + frame *ExecutionFrame // fold state objects. accuVal ref.Val @@ -1322,16 +1557,16 @@ func (f *folder) foldIterable(iterable traits.Iterable) ref.Val { for it.HasNext() == types.True { f.iterVar1Val = it.Next() - cond := f.cond.Eval(f) + cond := f.cond.Exec(f.frame) condBool, ok := cond.(types.Bool) if f.interrupted || (!f.exhaustive && ok && condBool != types.True) { return f.evalResult() } // Update the accumulation value and check for eval interuption. - f.accuVal = f.step.Eval(f) + f.accuVal = f.step.Exec(f.frame) f.initialized = true - if f.interruptable && checkInterrupt(f.activation) { + if f.interruptable && f.frame.CheckInterrupt() { f.interrupted = true return f.evalResult() } @@ -1348,16 +1583,16 @@ func (f *folder) FoldEntry(key, val any) bool { // Terminate evaluation if evaluation is interrupted or the condition is not true and exhaustive // eval is not enabled. - cond := f.cond.Eval(f) + cond := f.cond.Exec(f.frame) condBool, ok := cond.(types.Bool) if f.interrupted || (!f.exhaustive && ok && condBool != types.True) { return false } // Update the accumulation value and check for eval interuption. - f.accuVal = f.step.Eval(f) + f.accuVal = f.step.Exec(f.frame) f.initialized = true - if f.interruptable && checkInterrupt(f.activation) { + if f.interruptable && f.frame.CheckInterrupt() { f.interrupted = true return false } @@ -1371,7 +1606,7 @@ func (f *folder) ResolveName(name string) (any, bool) { if name == f.accuVar { if !f.initialized { f.initialized = true - initVal := f.accu.Eval(f.activation) + initVal := f.accu.Exec(f.frame.parent) if !f.exhaustive { if l, isList := initVal.(traits.Lister); isList && l.Size() == types.IntZero { initVal = types.NewMutableList(f.adapter) @@ -1396,18 +1631,43 @@ func (f *folder) ResolveName(name string) (any, bool) { return f.iterVar2Val, true } } - return f.activation.ResolveName(name) + return f.frame.parent.ResolveName(name) } // Parent returns the activation embedded into the folder. func (f *folder) Parent() Activation { - return f.activation + return f.frame.parent +} + +// Unwrap returns the parent activation, thus omitting access to local state +func (f *folder) Unwrap() Activation { + return f.frame.parent +} + +// IsLocalVariable reports whether the variable name is locally bound by the folder scope. +func (f *folder) IsLocalVariable(name string) bool { + if name == f.accuVar { + return true + } + if !f.computeResult && (name == f.iterVar || name == f.iterVar2) { + return true + } + parent := f.Parent() + if parent == nil { + return false + } + if varHolder, ok := parent.(localVariableHolder); ok { + if varHolder.IsLocalVariable(name) { + return true + } + } + return false } // 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 pv, ok := f.frame.parent.Activation.(partialActivationConverter); ok { if partial, isPartial := pv.AsPartialActivation(); isPartial { return partial.UnknownAttributePatterns() } @@ -1416,7 +1676,7 @@ func (f *folder) UnknownAttributePatterns() []*AttributePattern { } func (f *folder) AsPartialActivation() (PartialActivation, bool) { - if pv, ok := f.activation.(partialActivationConverter); ok { + if pv, ok := f.frame.parent.Activation.(partialActivationConverter); ok { if _, isPartial := pv.AsPartialActivation(); isPartial { return f, true } @@ -1428,9 +1688,9 @@ func (f *folder) AsPartialActivation() (PartialActivation, bool) { func (f *folder) evalResult() ref.Val { f.computeResult = true if f.interrupted { - return types.NewErr("operation interrupted") + return types.WrapErr(InterruptError{}) } - res := f.result.Eval(f) + res := f.result.Exec(f.frame) // Convert a mutable list or map to an immutable one if the comprehension has generated a list or // map as a result. if !types.IsUnknownOrError(res) && f.mutableValue { @@ -1447,7 +1707,8 @@ func (f *folder) evalResult() ref.Val { // reset clears any state associated with folder evaluation. func (f *folder) reset() { f.evalFold = nil - f.activation = nil + f.frame.Pop() + f.frame = nil f.accuVal = nil f.iterVar1Val = nil f.iterVar2Val = nil @@ -1458,9 +1719,18 @@ func (f *folder) reset() { f.computeResult = false } -func checkInterrupt(a Activation) bool { - stop, found := a.ResolveName("#interrupted") - return found && stop == true +// InterruptError is a specialized error type used to signal that program evaluation should check +// whether a context cancellation is responsible for the error. +type InterruptError struct{} + +// Error returns operation interrupted. +func (InterruptError) Error() string { + return "operation interrupted" +} + +// Is returns whether two errors are interrupt errors. +func (ie InterruptError) Is(target error) bool { + return target.Error() == ie.Error() } var ( diff --git a/vendor/github.com/google/cel-go/interpreter/interpreter.go b/vendor/github.com/google/cel-go/interpreter/interpreter.go index be57e7439..29df9d41e 100644 --- a/vendor/github.com/google/cel-go/interpreter/interpreter.go +++ b/vendor/github.com/google/cel-go/interpreter/interpreter.go @@ -29,11 +29,11 @@ import ( // PlannerOption configures the program plan options during interpretable setup. type PlannerOption func(*planner) (*planner, error) -// Interpreter generates a new Interpretable from a checked or unchecked expression. +// Interpreter generates a new InterpretableV2 from a checked or unchecked expression. type Interpreter interface { - // NewInterpretable creates an Interpretable from a checked expression and an + // NewInterpretable creates an InterpretableV2 from a checked expression and an // optional list of PlannerOption values. - NewInterpretable(exprAST *ast.AST, opts ...PlannerOption) (Interpretable, error) + NewInterpretable(exprAST *ast.AST, opts ...PlannerOption) (InterpretableV2, error) } // EvalObserver is a functional interface that accepts an expression id and an observed value. @@ -43,16 +43,16 @@ type EvalObserver func(vars Activation, id int64, programStep any, value ref.Val // StatefulObserver observes evaluation while tracking or utilizing stateful behavior. type StatefulObserver interface { - // InitState configures stateful metadata on the activation. - InitState(Activation) (Activation, error) + // InitState configures stateful metadata on the execution frame. + InitState(*ExecutionFrame) (any, error) - // GetState retrieves the stateful metadata from the activation. - GetState(Activation) any + // GetState retrieves the stateful metadata from the execution frame. + GetState(*ExecutionFrame) any // Observe passes the activation and relevant evaluation metadata to the observer. - // The observe method is expected to do the equivalent of GetState(vars) in order + // The observe method is expected to do the equivalent of GetState(AsFrame(activation)) // to find the metadata that needs to be updated upon invocation. - Observe(vars Activation, id int64, programStep any, value ref.Val) + Observe(Activation, int64, any, ref.Val) } // EvalCancelledError represents a cancelled program evaluation operation. @@ -106,46 +106,17 @@ func EvalStateObserver(opts ...evalStateOption) PlannerOption { } } -// evalStateConverter identifies an object which is convertible to an EvalState instance. -type evalStateConverter interface { - asEvalState() EvalState +// activationWrapper identifies an object carrying local variables which should not be exposed to the user +// Activations used for such purposes can be unwrapped to return the activation which omits local state. +type activationWrapper interface { + // Unwrap returns the Activation which omits local state. + Unwrap() Activation } -// evalStateActivation hides state in the Activation in a manner not accessible to expressions. -type evalStateActivation struct { - vars Activation - state EvalState -} - -// ResolveName proxies variable lookups to the backing activation. -func (esa evalStateActivation) ResolveName(name string) (any, bool) { - return esa.vars.ResolveName(name) -} - -// Parent proxies parent lookups to the backing activation. -func (esa evalStateActivation) Parent() Activation { - return esa.vars -} - -// AsPartialActivation supports conversion to a partial activation in order to detect unknown attributes. -func (esa evalStateActivation) AsPartialActivation() (PartialActivation, bool) { - return AsPartialActivation(esa.vars) -} - -// asEvalState implements the evalStateConverter method. -func (esa evalStateActivation) asEvalState() EvalState { - return esa.state -} - -// asEvalState walks the Activation hierarchy and returns the first EvalState found, if present. -func asEvalState(vars Activation) (EvalState, bool) { - if conv, ok := vars.(evalStateConverter); ok { - return conv.asEvalState(), true - } - if vars.Parent() != nil { - return asEvalState(vars.Parent()) - } - return nil, false +// localVariableHolder identifies an Activation scope that holds local variables and supports testing +// whether a variable name is local to this scope. +type localVariableHolder interface { + IsLocalVariable(name string) bool } // evalStateFactory holds a reference to a factory function that produces an EvalState instance. @@ -153,32 +124,54 @@ type evalStateFactory struct { factory func() EvalState } -// InitState produces an EvalState instance and bundles it into the Activation in a way which is +// InitState produces an EvalState instance and bundles it into the ExecutionFrame in a way which is // not visible to expression evaluation. -func (et *evalStateFactory) InitState(vars Activation) (Activation, error) { +func (et *evalStateFactory) InitState(frame *ExecutionFrame) (any, error) { + if frame.ctx != nil && frame.ctx.state != nil { + return frame.ctx.state, nil + } state := et.factory() - return evalStateActivation{vars: vars, state: state}, nil + if frame.ctx == nil { + frame.ctx = evalContextPool.Get().(*evalContext) + } + frame.ctx.state = state + return state, nil } // GetState extracts the EvalState from the Activation. -func (et *evalStateFactory) GetState(vars Activation) any { - if state, found := asEvalState(vars); found { - return state +func (et *evalStateFactory) GetState(frame *ExecutionFrame) any { + if frame.ctx == nil { + return nil } - return nil + return frame.ctx.state } // Observe records the evaluation state for a given expression node and program step. func (et *evalStateFactory) Observe(vars Activation, id int64, programStep any, val ref.Val) { - state, found := asEvalState(vars) - if !found { + frame := AsFrame(vars) + if frame.ctx == nil || frame.ctx.state == nil { return } - state.SetValue(id, val) + frame.ctx.state.SetValue(id, val) } // CustomDecorator configures a custom interpretable decorator for the program. func CustomDecorator(dec InterpretableDecorator) PlannerOption { + return func(p *planner) (*planner, error) { + dec2 := func(i InterpretableV2) (InterpretableV2, error) { + legacy, err := dec(i) + if err != nil { + return nil, err + } + return adaptToV2(legacy), nil + } + p.decorators = append(p.decorators, dec2) + return p, nil + } +} + +// CustomDecoratorV2 configures a custom V2 interpretable decorator for the program. +func CustomDecoratorV2(dec InterpretableDecoratorV2) PlannerOption { return func(p *planner) (*planner, error) { p.decorators = append(p.decorators, dec) return p, nil @@ -191,7 +184,7 @@ func CustomDecorator(dec InterpretableDecorator) PlannerOption { // provided to the decorator. This decorator is not thread-safe, and the EvalState // must be reset between Eval() calls. func ExhaustiveEval() PlannerOption { - return CustomDecorator(decDisableShortcircuits()) + return CustomDecoratorV2(decDisableShortcircuits()) } // InterruptableEval annotates comprehension loops with information that indicates they @@ -200,13 +193,13 @@ func ExhaustiveEval() PlannerOption { // The custom activation is currently managed higher up in the stack within the 'cel' package // and should not require any custom support on behalf of callers. func InterruptableEval() PlannerOption { - return CustomDecorator(decInterruptFolds()) + return CustomDecoratorV2(decInterruptFolds()) } // Optimize will pre-compute operations such as list and map construction and optimize // call arguments to set membership tests. The set of optimizations will increase over time. func Optimize() PlannerOption { - return CustomDecorator(decOptimize()) + return CustomDecoratorV2(decOptimize()) } // RegexOptimization provides a way to replace an InterpretableCall for a regex function when the @@ -231,7 +224,12 @@ type RegexOptimization struct { // CompileRegexConstants compiles regex pattern string constants at program creation time and reports any regex pattern // compile errors. func CompileRegexConstants(regexOptimizations ...*RegexOptimization) PlannerOption { - return CustomDecorator(decRegexOptimizer(regexOptimizations...)) + return CustomDecoratorV2(decRegexOptimizer(regexOptimizations...)) +} + +// RegexProgramSizeLimit caps the maximum regex program plan size permitted during evaluation. +func RegexProgramSizeLimit(limit int) PlannerOption { + return CustomDecoratorV2(decRegexProgramSizeLimit(limit)) } type exprInterpreter struct { @@ -257,10 +255,10 @@ func NewInterpreter(dispatcher Dispatcher, attrFactory: attrFactory} } -// NewIntepretable implements the Interpreter interface method. +// NewInterpretable implements the Interpreter interface method. func (i *exprInterpreter) NewInterpretable( checked *ast.AST, - opts ...PlannerOption) (Interpretable, error) { + opts ...PlannerOption) (InterpretableV2, error) { p := newPlanner(i.dispatcher, i.provider, i.adapter, i.attrFactory, i.container, checked) var err error for _, o := range opts { diff --git a/vendor/github.com/google/cel-go/interpreter/planner.go b/vendor/github.com/google/cel-go/interpreter/planner.go index f0e0d4305..396a9803f 100644 --- a/vendor/github.com/google/cel-go/interpreter/planner.go +++ b/vendor/github.com/google/cel-go/interpreter/planner.go @@ -43,7 +43,7 @@ func newPlanner(disp Dispatcher, container: cont, refMap: exprAST.ReferenceMap(), typeMap: exprAST.TypeMap(), - decorators: make([]InterpretableDecorator, 0), + decorators: make([]InterpretableDecoratorV2, 0), observers: make([]StatefulObserver, 0), } } @@ -57,27 +57,34 @@ type planner struct { container *containers.Container refMap map[int64]*ast.ReferenceInfo typeMap map[int64]*types.Type - decorators []InterpretableDecorator + decorators []InterpretableDecoratorV2 observers []StatefulObserver } +type planBuilder struct { + *planner + + localVars map[string]int +} + // Plan implements the interpretablePlanner interface. This implementation of the Plan method also // applies decorators to each Interpretable generated as part of the overall plan. Decorators are // useful for layering functionality into the evaluation that is not natively understood by CEL, // such as state-tracking, expression re-write, and possibly efficient thread-safe memoization of // repeated expressions. -func (p *planner) Plan(expr ast.Expr) (Interpretable, error) { - i, err := p.plan(expr) +func (p *planner) Plan(expr ast.Expr) (InterpretableV2, error) { + pb := &planBuilder{planner: p, localVars: make(map[string]int)} + i, err := pb.plan(expr) if err != nil { return nil, err } if len(p.observers) == 0 { return i, nil } - return &ObservableInterpretable{Interpretable: i, observers: p.observers}, nil + return &ObservableInterpretable{InterpretableV2: i, observers: p.observers}, nil } -func (p *planner) plan(expr ast.Expr) (Interpretable, error) { +func (p *planBuilder) plan(expr ast.Expr) (InterpretableV2, error) { switch expr.Kind() { case ast.CallKind: return p.decorate(p.planCall(expr)) @@ -102,7 +109,7 @@ func (p *planner) plan(expr ast.Expr) (Interpretable, error) { // decorate applies the InterpretableDecorator functions to the given Interpretable. // Both the Interpretable and error generated by a Plan step are accepted as arguments // for convenience. -func (p *planner) decorate(i Interpretable, err error) (Interpretable, error) { +func (p *planBuilder) decorate(i InterpretableV2, err error) (InterpretableV2, error) { if err != nil { return nil, err } @@ -116,20 +123,26 @@ func (p *planner) decorate(i Interpretable, err error) (Interpretable, error) { } // planIdent creates an Interpretable that resolves an identifier from an Activation. -func (p *planner) planIdent(expr ast.Expr) (Interpretable, error) { +func (p *planBuilder) planIdent(expr ast.Expr) (InterpretableV2, error) { // Establish whether the identifier is in the reference map. if identRef, found := p.refMap[expr.ID()]; found { return p.planCheckedIdent(expr.ID(), identRef) } // Create the possible attribute list for the unresolved reference. ident := expr.AsIdent() + if p.isLocalVar(ident) { + return &evalAttr{ + adapter: p.adapter, + attr: p.attrFactory.AbsoluteAttribute(expr.ID(), ident), + }, nil + } return &evalAttr{ adapter: p.adapter, attr: p.attrFactory.MaybeAttribute(expr.ID(), ident), }, nil } -func (p *planner) planCheckedIdent(id int64, identRef *ast.ReferenceInfo) (Interpretable, error) { +func (p *planBuilder) planCheckedIdent(id int64, identRef *ast.ReferenceInfo) (InterpretableV2, error) { // Plan a constant reference if this is the case for this simple identifier. if identRef.Value != nil { return NewConstValue(id, identRef.Value), nil @@ -158,7 +171,7 @@ func (p *planner) planCheckedIdent(id int64, identRef *ast.ReferenceInfo) (Inter // a) selects a field from a map or proto. // b) creates a field presence test for a select within a has() macro. // c) resolves the select expression to a namespaced identifier. -func (p *planner) planSelect(expr ast.Expr) (Interpretable, error) { +func (p *planBuilder) planSelect(expr ast.Expr) (InterpretableV2, error) { // If the Select id appears in the reference map from the CheckedExpr proto then it is either // a namespaced identifier or enum value. if identRef, found := p.refMap[expr.ID()]; found { @@ -214,7 +227,7 @@ func (p *planner) planSelect(expr ast.Expr) (Interpretable, error) { // planCall creates a callable Interpretable while specializing for common functions and invocation // patterns. Specifically, conditional operators &&, ||, ?:, and (in)equality functions result in // optimized Interpretable values. -func (p *planner) planCall(expr ast.Expr) (Interpretable, error) { +func (p *planBuilder) planCall(expr ast.Expr) (InterpretableV2, error) { call := expr.AsCall() target, fnName, oName := p.resolveFunction(expr) argCount := len(call.Args()) @@ -224,7 +237,7 @@ func (p *planner) planCall(expr ast.Expr) (Interpretable, error) { offset++ } - args := make([]Interpretable, argCount) + args := make([]InterpretableV2, argCount) if target != nil { arg, err := p.plan(target) if err != nil { @@ -268,6 +281,10 @@ func (p *planner) planCall(expr ast.Expr) (Interpretable, error) { if fnDef == nil { fnDef, _ = p.disp.FindOverload(fnName) } + // Async overloads are planned into an evalAsyncFunc regardless of arity. + if fnDef != nil && fnDef.Async != nil { + return p.planCallAsync(expr, fnName, oName, fnDef, args) + } switch argCount { case 0: return p.planCallZero(expr, fnName, oName, fnDef) @@ -290,11 +307,29 @@ func (p *planner) planCall(expr ast.Expr) (Interpretable, error) { } } +// planCallAsync generates an asynchronous callable Interpretable. +func (p *planBuilder) planCallAsync(expr ast.Expr, + function string, + overload string, + impl *functions.Overload, + args []InterpretableV2) (InterpretableV2, error) { + if impl == nil || impl.Async == nil { + return nil, fmt.Errorf("no such overload: %s()", function) + } + return &evalAsyncFunc{ + id: expr.ID(), + function: function, + overload: overload, + args: args, + impl: impl.Async, + }, nil +} + // planCallZero generates a zero-arity callable Interpretable. -func (p *planner) planCallZero(expr ast.Expr, +func (p *planBuilder) planCallZero(expr ast.Expr, function string, overload string, - impl *functions.Overload) (Interpretable, error) { + impl *functions.Overload) (InterpretableV2, error) { if impl == nil || impl.Function == nil { return nil, fmt.Errorf("no such overload: %s()", function) } @@ -307,11 +342,11 @@ func (p *planner) planCallZero(expr ast.Expr, } // planCallUnary generates a unary callable Interpretable. -func (p *planner) planCallUnary(expr ast.Expr, +func (p *planBuilder) planCallUnary(expr ast.Expr, function string, overload string, impl *functions.Overload, - args []Interpretable) (Interpretable, error) { + args []InterpretableV2) (InterpretableV2, error) { var fn functions.UnaryOp var trait int var nonStrict bool @@ -335,11 +370,11 @@ func (p *planner) planCallUnary(expr ast.Expr, } // planCallBinary generates a binary callable Interpretable. -func (p *planner) planCallBinary(expr ast.Expr, +func (p *planBuilder) planCallBinary(expr ast.Expr, function string, overload string, impl *functions.Overload, - args []Interpretable) (Interpretable, error) { + args []InterpretableV2) (InterpretableV2, error) { var fn functions.BinaryOp var trait int var nonStrict bool @@ -364,11 +399,11 @@ func (p *planner) planCallBinary(expr ast.Expr, } // planCallVarArgs generates a variable argument callable Interpretable. -func (p *planner) planCallVarArgs(expr ast.Expr, +func (p *planBuilder) planCallVarArgs(expr ast.Expr, function string, overload string, impl *functions.Overload, - args []Interpretable) (Interpretable, error) { + args []InterpretableV2) (InterpretableV2, error) { var fn functions.FunctionOp var trait int var nonStrict bool @@ -392,7 +427,7 @@ func (p *planner) planCallVarArgs(expr ast.Expr, } // planCallEqual generates an equals (==) Interpretable. -func (p *planner) planCallEqual(expr ast.Expr, args []Interpretable) (Interpretable, error) { +func (p *planBuilder) planCallEqual(expr ast.Expr, args []InterpretableV2) (InterpretableV2, error) { return &evalEq{ id: expr.ID(), lhs: args[0], @@ -401,7 +436,7 @@ func (p *planner) planCallEqual(expr ast.Expr, args []Interpretable) (Interpreta } // planCallNotEqual generates a not equals (!=) Interpretable. -func (p *planner) planCallNotEqual(expr ast.Expr, args []Interpretable) (Interpretable, error) { +func (p *planBuilder) planCallNotEqual(expr ast.Expr, args []InterpretableV2) (InterpretableV2, error) { return &evalNe{ id: expr.ID(), lhs: args[0], @@ -410,7 +445,7 @@ func (p *planner) planCallNotEqual(expr ast.Expr, args []Interpretable) (Interpr } // planCallLogicalAnd generates a logical and (&&) Interpretable. -func (p *planner) planCallLogicalAnd(expr ast.Expr, args []Interpretable) (Interpretable, error) { +func (p *planBuilder) planCallLogicalAnd(expr ast.Expr, args []InterpretableV2) (InterpretableV2, error) { return &evalAnd{ id: expr.ID(), terms: args, @@ -418,7 +453,7 @@ func (p *planner) planCallLogicalAnd(expr ast.Expr, args []Interpretable) (Inter } // planCallLogicalOr generates a logical or (||) Interpretable. -func (p *planner) planCallLogicalOr(expr ast.Expr, args []Interpretable) (Interpretable, error) { +func (p *planBuilder) planCallLogicalOr(expr ast.Expr, args []InterpretableV2) (InterpretableV2, error) { return &evalOr{ id: expr.ID(), terms: args, @@ -426,7 +461,7 @@ func (p *planner) planCallLogicalOr(expr ast.Expr, args []Interpretable) (Interp } // planCallConditional generates a conditional / ternary (c ? t : f) Interpretable. -func (p *planner) planCallConditional(expr ast.Expr, args []Interpretable) (Interpretable, error) { +func (p *planBuilder) planCallConditional(expr ast.Expr, args []InterpretableV2) (InterpretableV2, error) { cond := args[0] t := args[1] var tAttr Attribute @@ -454,7 +489,7 @@ func (p *planner) planCallConditional(expr ast.Expr, args []Interpretable) (Inte // planCallIndex either extends an attribute with the argument to the index operation, or creates // a relative attribute based on the return of a function call or operation. -func (p *planner) planCallIndex(expr ast.Expr, args []Interpretable, optional bool) (Interpretable, error) { +func (p *planBuilder) planCallIndex(expr ast.Expr, args []InterpretableV2, optional bool) (InterpretableV2, error) { op := args[0] ind := args[1] opType := p.typeMap[op.ID()] @@ -489,7 +524,7 @@ func (p *planner) planCallIndex(expr ast.Expr, args []Interpretable, optional bo } // planCreateList generates a list construction Interpretable. -func (p *planner) planCreateList(expr ast.Expr) (Interpretable, error) { +func (p *planBuilder) planCreateList(expr ast.Expr) (InterpretableV2, error) { list := expr.AsList() optionalIndices := list.OptionalIndices() elements := list.Elements() @@ -500,7 +535,7 @@ func (p *planner) planCreateList(expr ast.Expr) (Interpretable, error) { } optionals[index] = true } - elems := make([]Interpretable, len(elements)) + elems := make([]InterpretableV2, len(elements)) for i, elem := range elements { elemVal, err := p.plan(elem) if err != nil { @@ -518,12 +553,12 @@ func (p *planner) planCreateList(expr ast.Expr) (Interpretable, error) { } // planCreateStruct generates a map or object construction Interpretable. -func (p *planner) planCreateMap(expr ast.Expr) (Interpretable, error) { +func (p *planBuilder) planCreateMap(expr ast.Expr) (InterpretableV2, error) { m := expr.AsMap() entries := m.Entries() optionals := make([]bool, len(entries)) - keys := make([]Interpretable, len(entries)) - vals := make([]Interpretable, len(entries)) + keys := make([]InterpretableV2, len(entries)) + vals := make([]InterpretableV2, len(entries)) hasOptionals := false for i, e := range entries { entry := e.AsMapEntry() @@ -552,7 +587,7 @@ func (p *planner) planCreateMap(expr ast.Expr) (Interpretable, error) { } // planCreateObj generates an object construction Interpretable. -func (p *planner) planCreateStruct(expr ast.Expr) (Interpretable, error) { +func (p *planBuilder) planCreateStruct(expr ast.Expr) (InterpretableV2, error) { obj := expr.AsStruct() typeName, defined := p.resolveTypeName(obj.TypeName()) if !defined { @@ -561,7 +596,7 @@ func (p *planner) planCreateStruct(expr ast.Expr) (Interpretable, error) { objFields := obj.Fields() optionals := make([]bool, len(objFields)) fields := make([]string, len(objFields)) - vals := make([]Interpretable, len(objFields)) + vals := make([]InterpretableV2, len(objFields)) hasOptionals := false for i, f := range objFields { field := f.AsStructField() @@ -586,7 +621,7 @@ func (p *planner) planCreateStruct(expr ast.Expr) (Interpretable, error) { } // planComprehension generates an Interpretable fold operation. -func (p *planner) planComprehension(expr ast.Expr) (Interpretable, error) { +func (p *planBuilder) planComprehension(expr ast.Expr) (InterpretableV2, error) { fold := expr.AsComprehension() accu, err := p.plan(fold.AccuInit()) if err != nil { @@ -596,6 +631,7 @@ func (p *planner) planComprehension(expr ast.Expr) (Interpretable, error) { if err != nil { return nil, err } + p.pushLocalVars(fold.AccuVar(), fold.IterVar(), fold.IterVar2()) cond, err := p.plan(fold.LoopCondition()) if err != nil { return nil, err @@ -604,10 +640,12 @@ func (p *planner) planComprehension(expr ast.Expr) (Interpretable, error) { if err != nil { return nil, err } + p.popLocalVars(fold.IterVar(), fold.IterVar2()) result, err := p.plan(fold.Result()) if err != nil { return nil, err } + p.popLocalVars(fold.AccuVar()) return &evalFold{ id: expr.ID(), accuVar: fold.AccuVar(), @@ -623,13 +661,13 @@ func (p *planner) planComprehension(expr ast.Expr) (Interpretable, error) { } // planConst generates a constant valued Interpretable. -func (p *planner) planConst(expr ast.Expr) (Interpretable, error) { +func (p *planBuilder) planConst(expr ast.Expr) (InterpretableV2, error) { return NewConstValue(expr.ID(), expr.AsLiteral()), nil } // resolveTypeName takes a qualified string constructed at parse time, applies the proto // namespace resolution rules to it in a scan over possible matching types in the TypeProvider. -func (p *planner) resolveTypeName(typeName string) (string, bool) { +func (p *planBuilder) resolveTypeName(typeName string) (string, bool) { for _, qualifiedTypeName := range p.container.ResolveCandidateNames(typeName) { if _, found := p.provider.FindStructType(qualifiedTypeName); found { return qualifiedTypeName, true @@ -646,7 +684,7 @@ func (p *planner) resolveTypeName(typeName string) (string, bool) { // - The target expression may only consist of ident and select expressions. // - The function is declared in the environment using its fully-qualified name. // - The fully-qualified function name matches the string serialized target value. -func (p *planner) resolveFunction(expr ast.Expr) (ast.Expr, string, string) { +func (p *planBuilder) resolveFunction(expr ast.Expr) (ast.Expr, string, string) { // Note: similar logic exists within the `checker/checker.go`. If making changes here // please consider the impact on checker.go and consolidate implementations or mirror code // as appropriate. @@ -687,7 +725,7 @@ func (p *planner) resolveFunction(expr ast.Expr) (ast.Expr, string, string) { // namespaced identifiers must be stripped, as all declarations already use fully-qualified // names. This stripping behavior is handled automatically by the ResolveCandidateNames // call. - return target, stripLeadingDot(fnName), "" + return target, strings.TrimPrefix(fnName, "."), "" } // Handle the situation where the function target actually indicates a qualified function name. @@ -710,7 +748,7 @@ func (p *planner) resolveFunction(expr ast.Expr) (ast.Expr, string, string) { // relativeAttr indicates that the attribute in this case acts as a qualifier and as such needs to // be observed to ensure that it's evaluation value is properly recorded for state tracking. -func (p *planner) relativeAttr(id int64, eval Interpretable, opt bool) (InterpretableAttribute, error) { +func (p *planBuilder) relativeAttr(id int64, eval InterpretableV2, opt bool) (InterpretableAttribute, error) { eAttr, ok := eval.(InterpretableAttribute) if !ok { eAttr = &evalAttr{ @@ -733,7 +771,7 @@ func (p *planner) relativeAttr(id int64, eval Interpretable, opt bool) (Interpre // toQualifiedName converts an expression AST into a qualified name if possible, with a boolean // 'found' value that indicates if the conversion is successful. -func (p *planner) toQualifiedName(operand ast.Expr) (string, bool) { +func (p *planBuilder) toQualifiedName(operand ast.Expr) (string, bool) { // If the checker identified the expression as an attribute by the type-checker, then it can't // possibly be part of qualified name in a namespace. _, isAttr := p.refMap[operand.ID()] @@ -759,9 +797,35 @@ func (p *planner) toQualifiedName(operand ast.Expr) (string, bool) { return "", false } -func stripLeadingDot(name string) string { - if strings.HasPrefix(name, ".") { - return name[1:] +func (p *planBuilder) pushLocalVars(names ...string) { + for _, name := range names { + if name == "" { + continue + } + if cnt, found := p.localVars[name]; found { + p.localVars[name] = cnt + 1 + } else { + p.localVars[name] = 1 + } } - return name +} + +func (p *planBuilder) popLocalVars(names ...string) { + for _, name := range names { + if name == "" { + continue + } + if cnt, found := p.localVars[name]; found { + if cnt == 1 { + delete(p.localVars, name) + } else { + p.localVars[name] = cnt - 1 + } + } + } +} + +func (p *planBuilder) isLocalVar(name string) bool { + _, found := p.localVars[name] + return found } diff --git a/vendor/github.com/google/cel-go/interpreter/runtimecost.go b/vendor/github.com/google/cel-go/interpreter/runtimecost.go index 6c44cd798..81e4ef63c 100644 --- a/vendor/github.com/google/cel-go/interpreter/runtimecost.go +++ b/vendor/github.com/google/cel-go/interpreter/runtimecost.go @@ -62,48 +62,6 @@ func CostObserver(opts ...costTrackPlanOption) PlannerOption { } } -// costTrackerConverter identifies an object which is convertible to a CostTracker instance. -type costTrackerConverter interface { - asCostTracker() *CostTracker -} - -// costTrackActivation hides state in the Activation in a manner not accessible to expressions. -type costTrackActivation struct { - vars Activation - costTracker *CostTracker -} - -// ResolveName proxies variable lookups to the backing activation. -func (cta costTrackActivation) ResolveName(name string) (any, bool) { - return cta.vars.ResolveName(name) -} - -// Parent proxies parent lookups to the backing activation. -func (cta costTrackActivation) Parent() Activation { - return cta.vars -} - -// AsPartialActivation supports conversion to a partial activation in order to detect unknown attributes. -func (cta costTrackActivation) AsPartialActivation() (PartialActivation, bool) { - return AsPartialActivation(cta.vars) -} - -// asCostTracker implements the costTrackerConverter method. -func (cta costTrackActivation) asCostTracker() *CostTracker { - return cta.costTracker -} - -// asCostTracker walks the Activation hierarchy and returns the first cost tracker found, if present. -func asCostTracker(vars Activation) (*CostTracker, bool) { - if conv, ok := vars.(costTrackerConverter); ok { - return conv.asCostTracker(), true - } - if vars.Parent() != nil { - return asCostTracker(vars.Parent()) - } - return nil, false -} - // costTrackerFactory holds a factory for producing new CostTracker instances on each Eval call. type costTrackerFactory struct { factory func() (*CostTracker, error) @@ -111,27 +69,40 @@ type costTrackerFactory struct { // InitState produces a CostTracker and bundles it into an Activation in a way which is not visible // to expression evaluation. -func (ct *costTrackerFactory) InitState(vars Activation) (Activation, error) { +func (ct *costTrackerFactory) InitState(frame *ExecutionFrame) (any, error) { + if frame.ctx != nil && frame.ctx.costs != nil { + return frame.ctx.costs, nil + } tracker, err := ct.factory() if err != nil { return nil, err } - return costTrackActivation{vars: vars, costTracker: tracker}, nil + if frame.ctx == nil { + frame.ctx = evalContextPool.Get().(*evalContext) + } + frame.ctx.costs = tracker + return tracker, nil } // GetState extracts the CostTracker from the Activation. -func (ct *costTrackerFactory) GetState(vars Activation) any { - if tracker, found := asCostTracker(vars); found { - return tracker +func (ct *costTrackerFactory) GetState(frame *ExecutionFrame) any { + if frame == nil || frame.ctx == nil { + return nil } - return nil + return frame.ctx.costs } // Observe computes the incremental cost of each step and records it into the CostTracker associated // with the evaluation. func (ct *costTrackerFactory) Observe(vars Activation, id int64, programStep any, val ref.Val) { - tracker, found := asCostTracker(vars) - if !found { + frame := AsFrame(vars) + state := ct.GetState(frame) + if state == nil { + return + } + tracker, ok := state.(*CostTracker) + if !ok { + // The state is configured with CostTrackFactory so this shouldn't happen. return } switch t := programStep.(type) { @@ -265,6 +236,19 @@ type CostTracker struct { stack refValStack } +// Clone makes a shallow copy of the tracker. +// The different clones can be used independently from +// each other. +func (c *CostTracker) Clone() (*CostTracker, error) { + tracker := &CostTracker{ + Estimator: c.Estimator, + overloadTrackers: c.overloadTrackers, + Limit: c.Limit, + presenceTestHasCost: c.presenceTestHasCost, + } + return tracker, nil +} + // ActualCost returns the runtime cost func (c *CostTracker) ActualCost() uint64 { return c.cost @@ -276,7 +260,7 @@ func (c *CostTracker) costCall(call InterpretableCall, args []ref.Val, result re if tracker, found := c.overloadTrackers[call.OverloadID()]; found { callCost := tracker(args, result) if callCost != nil { - cost += *callCost + cost = safeAdd(cost, *callCost) return cost } } @@ -284,7 +268,7 @@ func (c *CostTracker) costCall(call InterpretableCall, args []ref.Val, result re if c.Estimator != nil { callCost := c.Estimator.CallCost(call.Function(), call.OverloadID(), args, result) if callCost != nil { - cost += *callCost + cost = safeAdd(cost, *callCost) return cost } } @@ -292,12 +276,14 @@ func (c *CostTracker) costCall(call InterpretableCall, args []ref.Val, result re // if user has their own implementation of ActualCostEstimator, make sure to cover the mapping between overloadId and cost calculation switch call.OverloadID() { // O(n) functions - case overloads.StartsWithString, overloads.EndsWithString, overloads.StringToBytes, overloads.BytesToString, overloads.ExtQuoteString, overloads.ExtFormatString: - cost += uint64(math.Ceil(float64(actualSize(args[0])) * common.StringTraversalCostFactor)) + case overloads.StartsWithString, overloads.EndsWithString: + cost = safeAdd(cost, uint64(math.Ceil(float64(actualSize(args[1]))*common.StringTraversalCostFactor))) + case overloads.StringToBytes, overloads.BytesToString, overloads.ExtQuoteString, overloads.ExtFormatString: + cost = safeAdd(cost, uint64(math.Ceil(float64(actualSize(args[0]))*common.StringTraversalCostFactor))) case overloads.InList: // 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). - cost += actualSize(args[1]) + cost = safeAdd(cost, actualSize(args[1])) // O(min(m, n)) functions case overloads.LessString, overloads.GreaterString, overloads.LessEqualsString, overloads.GreaterEqualsString, overloads.LessBytes, overloads.GreaterBytes, overloads.LessEqualsBytes, overloads.GreaterEqualsBytes, @@ -307,17 +293,14 @@ func (c *CostTracker) costCall(call InterpretableCall, args []ref.Val, result re // of 1. lhsSize := actualSize(args[0]) rhsSize := actualSize(args[1]) - minSize := lhsSize - if rhsSize < minSize { - minSize = rhsSize - } - cost += uint64(math.Ceil(float64(minSize) * common.StringTraversalCostFactor)) + minSize := min(rhsSize, lhsSize) + cost = safeAdd(cost, uint64(math.Ceil(float64(minSize)*common.StringTraversalCostFactor))) // O(m+n) functions case overloads.AddString, overloads.AddBytes: // In the worst case scenario, we would need to reallocate a new backing store and copy both operands over. - cost += uint64(math.Ceil(float64(actualSize(args[0])+actualSize(args[1])) * common.StringTraversalCostFactor)) + cost = safeAdd(cost, uint64(math.Ceil(float64(actualSize(args[0])+actualSize(args[1]))*common.StringTraversalCostFactor))) // O(nm) functions - case overloads.MatchesString: + case overloads.Matches, overloads.MatchesString: // 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 // in case where string is empty but regex is still expensive. @@ -328,11 +311,11 @@ func (c *CostTracker) costCall(call InterpretableCall, args []ref.Val, result re // For now, we're making a guess that each expression in a regex is typically at least 4 chars // in length. regexCost := uint64(math.Ceil(float64(actualSize(args[1])) * common.RegexStringLengthCostFactor)) - cost += strCost * regexCost + cost = safeAdd(cost, strCost*regexCost) case overloads.ContainsString: strCost := uint64(math.Ceil(float64(actualSize(args[0])) * common.StringTraversalCostFactor)) substrCost := uint64(math.Ceil(float64(actualSize(args[1])) * common.StringTraversalCostFactor)) - cost += strCost * substrCost + cost = safeAdd(cost, strCost*substrCost) default: // The following operations are assumed to have O(1) complexity. @@ -342,7 +325,7 @@ func (c *CostTracker) costCall(call InterpretableCall, args []ref.Val, result re // - Computing the size of strings, byte sequences, lists and maps. // - Logical operations and all operators on fixed width scalars (comparisons, equality) // - Any functions that don't have a declared cost either here or in provided ActualCostEstimator. - cost++ + cost = safeAdd(cost, 1) } return cost @@ -397,7 +380,7 @@ func (s *refValStack) drop(ids ...int64) { // the stack. // WARNING: It is possible for multiple expressions with the same ID to exist (due to how macros are implemented) so it's // possible that a dropped ID will remain on the stack. They should be removed when IDs on the stack are popped. -func (s *refValStack) dropArgs(args []Interpretable) ([]ref.Val, bool) { +func (s *refValStack) dropArgs(args []InterpretableV2) ([]ref.Val, bool) { result := make([]ref.Val, len(args)) argloop: for nIdx := len(args) - 1; nIdx >= 0; nIdx-- { @@ -413,3 +396,21 @@ argloop: } return result, true } + +func safeAdd(x, y uint64, rest ...uint64) uint64 { + if y > 0 && x > math.MaxUint64-y { + return math.MaxUint64 + } + next := x + y + if len(rest) == 0 { + return next + } + return safeAdd(next, rest[0], rest[1:]...) +} + +func safeMul(x, y uint64) uint64 { + if y != 0 && x > math.MaxUint64/y { + return math.MaxUint64 + } + return x * y +} diff --git a/vendor/github.com/google/cel-go/parser/helper.go b/vendor/github.com/google/cel-go/parser/helper.go index c13296dd5..84bef80d5 100644 --- a/vendor/github.com/google/cel-go/parser/helper.go +++ b/vendor/github.com/google/cel-go/parser/helper.go @@ -45,6 +45,10 @@ func (p *parserHelper) getSourceInfo() *ast.SourceInfo { return p.sourceInfo } +func (p *parserHelper) expressionCount() int64 { + return p.nextID - 1 +} + func (p *parserHelper) newLiteral(ctx any, value ref.Val) ast.Expr { return p.exprFactory.NewLiteral(p.newID(ctx), value) } @@ -159,7 +163,7 @@ func (p *parserHelper) id(ctx any) int64 { offset.Start = p.sourceInfo.ComputeOffset(int32(c.GetLine()), int32(c.GetColumn())) offset.Stop = offset.Start + int32(len(c.GetText())) case common.Location: - offset.Start = p.sourceInfo.ComputeOffset(int32(c.Line()), int32(c.Column())) + offset.Start = p.sourceInfo.ComputeOffsetAbsolute(int32(c.Line()), int32(c.Column())) offset.Stop = offset.Start case ast.OffsetRange: offset = c diff --git a/vendor/github.com/google/cel-go/parser/options.go b/vendor/github.com/google/cel-go/parser/options.go index 4eb30f83e..281021f12 100644 --- a/vendor/github.com/google/cel-go/parser/options.go +++ b/vendor/github.com/google/cel-go/parser/options.go @@ -22,6 +22,7 @@ type options struct { errorRecoveryTokenLookaheadLimit int errorRecoveryLimit int expressionSizeCodePointLimit int + maxExpressionNodeCount int macros map[string]Macro populateMacroCalls bool enableOptionalSyntax bool @@ -97,6 +98,18 @@ func ExpressionSizeCodePointLimit(expressionSizeCodePointLimit int) Option { } } +// MaxExpressionNodeCount limits the maximum number of expression nodes that may be emitted by the parser, +// including nodes created by macro expansion. +func MaxExpressionNodeCount(limit int) Option { + return func(opts *options) error { + if limit < -1 { + return fmt.Errorf("max expression node count must be greater than or equal to -1: %d", limit) + } + opts.maxExpressionNodeCount = limit + return nil + } +} + // Macros adds the given macros to the parser. func Macros(macros ...Macro) Option { return func(opts *options) error { diff --git a/vendor/github.com/google/cel-go/parser/parser.go b/vendor/github.com/google/cel-go/parser/parser.go index b5ec73ec6..338233543 100644 --- a/vendor/github.com/google/cel-go/parser/parser.go +++ b/vendor/github.com/google/cel-go/parser/parser.go @@ -42,6 +42,7 @@ type Parser struct { func NewParser(opts ...Option) (*Parser, error) { p := &Parser{} p.enableHiddenAccumulatorName = true + p.enableIdentEscapeSyntax = true for _, opt := range opts { if err := opt(&p.options); err != nil { return nil, err @@ -71,6 +72,12 @@ func NewParser(opts ...Option) (*Parser, error) { if p.expressionSizeCodePointLimit == -1 { p.expressionSizeCodePointLimit = int((^uint(0)) >> 1) } + if p.maxExpressionNodeCount == 0 { + p.maxExpressionNodeCount = 100_000 + } + if p.maxExpressionNodeCount == -1 { + p.maxExpressionNodeCount = int((^uint(0)) >> 1) + } // Bool is false by default, so populateMacroCalls will be false by default return p, nil } @@ -101,6 +108,7 @@ func (p *Parser) Parse(source common.Source) (*ast.AST, *common.Errors) { helper: newParserHelper(source, fac), macros: p.macros, maxRecursionDepth: p.maxRecursionDepth, + maxExpressionNodeCount: p.maxExpressionNodeCount, errorReportingLimit: p.errorReportingLimit, errorRecoveryLimit: p.errorRecoveryLimit, errorRecoveryLookaheadTokenLimit: p.errorRecoveryTokenLookaheadLimit, @@ -318,6 +326,7 @@ type parser struct { recursionDepth int errorReports int maxRecursionDepth int + maxExpressionNodeCount int errorReportingLimit int errorRecoveryLimit int errorRecoveryLookaheadTokenLimit int @@ -963,11 +972,21 @@ func (p *parser) expandMacro(exprID int64, function string, target ast.Expr, arg return nil, false } } + if int(p.helper.expressionCount()) > p.maxExpressionNodeCount { + loc := p.helper.getLocation(exprID) + p.helper.deleteID(exprID) + return p.reportError(loc, "expression count exceeds limit of %d while expanding macro '%s'", p.maxExpressionNodeCount, function), true + } eh := exprHelperPool.Get().(*exprHelper) defer exprHelperPool.Put(eh) eh.parserHelper = p.helper eh.id = exprID expr, err := macro.Expander()(eh, target, args) + if int(p.helper.expressionCount()) > p.maxExpressionNodeCount { + loc := p.helper.getLocation(exprID) + p.helper.deleteID(exprID) + return p.reportError(loc, "expression count exceeds limit of %d while expanding macro '%s'", p.maxExpressionNodeCount, function), true + } // An error indicates that the macro was matched, but the arguments were not well-formed. if err != nil { loc := err.Location diff --git a/vendor/github.com/google/cel-go/parser/unparser.go b/vendor/github.com/google/cel-go/parser/unparser.go index ffd5b18e4..d503a450e 100644 --- a/vendor/github.com/google/cel-go/parser/unparser.go +++ b/vendor/github.com/google/cel-go/parser/unparser.go @@ -297,7 +297,7 @@ func (un *unparser) visitConstVal(val ref.Val) error { // represent the float using the minimum required digits d := strconv.FormatFloat(float64(val), 'g', -1, 64) un.str.WriteString(d) - if !strings.Contains(d, ".") { + if !strings.ContainsAny(d, ".eE") { un.str.WriteString(".0") } case types.Int: diff --git a/vendor/github.com/google/gnostic-models/extensions/extension.proto b/vendor/github.com/google/gnostic-models/extensions/extension.proto index 875137c1a..a60042989 100644 --- a/vendor/github.com/google/gnostic-models/extensions/extension.proto +++ b/vendor/github.com/google/gnostic-models/extensions/extension.proto @@ -42,7 +42,7 @@ option java_package = "org.gnostic.v1"; option objc_class_prefix = "GNX"; // The Go package name. -option go_package = "./extensions;gnostic_extension_v1"; +option go_package = "github.com/google/gnostic-models/extensions;gnostic_extension_v1"; // The version number of Gnostic. message Version { diff --git a/vendor/github.com/google/gnostic-models/openapiv2/OpenAPIv2.proto b/vendor/github.com/google/gnostic-models/openapiv2/OpenAPIv2.proto index 1c59b2f4a..49adafcc8 100644 --- a/vendor/github.com/google/gnostic-models/openapiv2/OpenAPIv2.proto +++ b/vendor/github.com/google/gnostic-models/openapiv2/OpenAPIv2.proto @@ -42,7 +42,7 @@ option java_package = "org.openapi_v2"; option objc_class_prefix = "OAS"; // The Go package name. -option go_package = "./openapiv2;openapi_v2"; +option go_package = "github.com/google/gnostic-models/openapiv2;openapi_v2"; message AdditionalPropertiesItem { oneof oneof { diff --git a/vendor/github.com/google/gnostic-models/openapiv3/OpenAPIv3.proto b/vendor/github.com/google/gnostic-models/openapiv3/OpenAPIv3.proto index 1be335b89..af4b6254b 100644 --- a/vendor/github.com/google/gnostic-models/openapiv3/OpenAPIv3.proto +++ b/vendor/github.com/google/gnostic-models/openapiv3/OpenAPIv3.proto @@ -42,7 +42,7 @@ option java_package = "org.openapi_v3"; option objc_class_prefix = "OAS"; // The Go package name. -option go_package = "./openapiv3;openapi_v3"; +option go_package = "github.com/google/gnostic-models/openapiv3;openapi_v3"; message AdditionalPropertiesItem { oneof oneof { diff --git a/vendor/github.com/google/gnostic-models/openapiv3/annotations.proto b/vendor/github.com/google/gnostic-models/openapiv3/annotations.proto index 09ee0aac5..895b4567c 100644 --- a/vendor/github.com/google/gnostic-models/openapiv3/annotations.proto +++ b/vendor/github.com/google/gnostic-models/openapiv3/annotations.proto @@ -20,7 +20,7 @@ import "google/protobuf/descriptor.proto"; import "openapiv3/OpenAPIv3.proto"; // The Go package name. -option go_package = "./openapiv3;openapi_v3"; +option go_package = "github.com/google/gnostic-models/openapiv3;openapi_v3"; // This option lets the proto compiler generate Java code inside the package // name (see below) instead of inside an outer class. It creates a simpler // developer experience by reducing one-level of name nesting and be diff --git a/vendor/github.com/josharian/intern/README.md b/vendor/github.com/josharian/intern/README.md deleted file mode 100644 index ffc44b219..000000000 --- a/vendor/github.com/josharian/intern/README.md +++ /dev/null @@ -1,5 +0,0 @@ -Docs: https://godoc.org/github.com/josharian/intern - -See also [Go issue 5160](https://golang.org/issue/5160). - -License: MIT diff --git a/vendor/github.com/josharian/intern/intern.go b/vendor/github.com/josharian/intern/intern.go deleted file mode 100644 index 7acb1fe90..000000000 --- a/vendor/github.com/josharian/intern/intern.go +++ /dev/null @@ -1,44 +0,0 @@ -// Package intern interns strings. -// Interning is best effort only. -// Interned strings may be removed automatically -// at any time without notification. -// All functions may be called concurrently -// with themselves and each other. -package intern - -import "sync" - -var ( - pool sync.Pool = sync.Pool{ - New: func() interface{} { - return make(map[string]string) - }, - } -) - -// String returns s, interned. -func String(s string) string { - m := pool.Get().(map[string]string) - c, ok := m[s] - if ok { - pool.Put(m) - return c - } - m[s] = s - pool.Put(m) - return s -} - -// Bytes returns b converted to a string, interned. -func Bytes(b []byte) string { - m := pool.Get().(map[string]string) - c, ok := m[string(b)] - if ok { - pool.Put(m) - return c - } - s := string(b) - m[s] = s - pool.Put(m) - return s -} diff --git a/vendor/github.com/josharian/intern/license.md b/vendor/github.com/josharian/intern/license.md deleted file mode 100644 index 353d3055f..000000000 --- a/vendor/github.com/josharian/intern/license.md +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Josh Bleecher Snyder - -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. diff --git a/vendor/github.com/mailru/easyjson/.gitignore b/vendor/github.com/mailru/easyjson/.gitignore deleted file mode 100644 index fbfaf7a3f..000000000 --- a/vendor/github.com/mailru/easyjson/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -.root -*_easyjson.go -*.iml -.idea -*.swp -bin/* diff --git a/vendor/github.com/mailru/easyjson/LICENSE b/vendor/github.com/mailru/easyjson/LICENSE deleted file mode 100644 index fbff658f7..000000000 --- a/vendor/github.com/mailru/easyjson/LICENSE +++ /dev/null @@ -1,7 +0,0 @@ -Copyright (c) 2016 Mail.Ru Group - -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. diff --git a/vendor/github.com/mailru/easyjson/Makefile b/vendor/github.com/mailru/easyjson/Makefile deleted file mode 100644 index cc5ebbad3..000000000 --- a/vendor/github.com/mailru/easyjson/Makefile +++ /dev/null @@ -1,72 +0,0 @@ -all: test - -clean: - rm -rf bin - rm -rf tests/*_easyjson.go - rm -rf benchmark/*_easyjson.go - -build: - go build -o ./bin/easyjson ./easyjson - -generate: build - bin/easyjson -stubs \ - ./tests/snake.go \ - ./tests/data.go \ - ./tests/omitempty.go \ - ./tests/nothing.go \ - ./tests/named_type.go \ - ./tests/custom_map_key_type.go \ - ./tests/embedded_type.go \ - ./tests/reference_to_pointer.go \ - ./tests/html.go \ - ./tests/unknown_fields.go \ - ./tests/type_declaration.go \ - ./tests/type_declaration_skip.go \ - ./tests/members_escaped.go \ - ./tests/members_unescaped.go \ - ./tests/intern.go \ - ./tests/nocopy.go \ - ./tests/escaping.go - bin/easyjson -all \ - ./tests/data.go \ - ./tests/nothing.go \ - ./tests/errors.go \ - ./tests/html.go \ - ./tests/type_declaration_skip.go - bin/easyjson \ - ./tests/nested_easy.go \ - ./tests/named_type.go \ - ./tests/custom_map_key_type.go \ - ./tests/embedded_type.go \ - ./tests/reference_to_pointer.go \ - ./tests/key_marshaler_map.go \ - ./tests/unknown_fields.go \ - ./tests/type_declaration.go \ - ./tests/members_escaped.go \ - ./tests/intern.go \ - ./tests/nocopy.go \ - ./tests/escaping.go \ - ./tests/nested_marshaler.go - bin/easyjson -snake_case ./tests/snake.go - bin/easyjson -omit_empty ./tests/omitempty.go - bin/easyjson -build_tags=use_easyjson -disable_members_unescape ./benchmark/data.go - bin/easyjson -disallow_unknown_fields ./tests/disallow_unknown.go - bin/easyjson -disable_members_unescape ./tests/members_unescaped.go - -test: generate - go test \ - ./tests \ - ./jlexer \ - ./gen \ - ./buffer - cd benchmark && go test -benchmem -tags use_easyjson -bench . - golint -set_exit_status ./tests/*_easyjson.go - -bench-other: generate - cd benchmark && make - -bench-python: - benchmark/ujson.sh - - -.PHONY: clean generate test build diff --git a/vendor/github.com/mailru/easyjson/README.md b/vendor/github.com/mailru/easyjson/README.md deleted file mode 100644 index 943b9e4ce..000000000 --- a/vendor/github.com/mailru/easyjson/README.md +++ /dev/null @@ -1,408 +0,0 @@ -# easyjson [![Build Status](https://github.com/mailru/easyjson/actions/workflows/easyjson.yml/badge.svg)](https://github.com/mailru/easyjson/actions/workflows/easyjson.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/mailru/easyjson)](https://goreportcard.com/report/github.com/mailru/easyjson) - -Package easyjson provides a fast and easy way to marshal/unmarshal Go structs -to/from JSON without the use of reflection. In performance tests, easyjson -outperforms the standard `encoding/json` package by a factor of 4-5x, and other -JSON encoding packages by a factor of 2-3x. - -easyjson aims to keep generated Go code simple enough so that it can be easily -optimized or fixed. Another goal is to provide users with the ability to -customize the generated code by providing options not available with the -standard `encoding/json` package, such as generating "snake_case" names or -enabling `omitempty` behavior by default. - -## Usage -### Install: -```sh -# for Go < 1.17 -go get -u github.com/mailru/easyjson/... -``` -#### or -```sh -# for Go >= 1.17 -go get github.com/mailru/easyjson && go install github.com/mailru/easyjson/...@latest -``` -### Run: -```sh -easyjson -all .go -``` - -The above will generate `_easyjson.go` containing the appropriate marshaler and -unmarshaler funcs for all structs contained in `.go`. - -Please note that easyjson requires a full Go build environment and the `GOPATH` -environment variable to be set. This is because easyjson code generation -invokes `go run` on a temporary file (an approach to code generation borrowed -from [ffjson](https://github.com/pquerna/ffjson)). - -### Serialize -```go -someStruct := &SomeStruct{Field1: "val1", Field2: "val2"} -rawBytes, err := easyjson.Marshal(someStruct) -``` - -### Deserialize -```go -someStruct := &SomeStruct{} -err := easyjson.Unmarshal(rawBytes, someStruct) -``` - -Please see the [GoDoc](https://godoc.org/github.com/mailru/easyjson) -for more information and features. -## Options -```txt -Usage of easyjson: - -all - generate marshaler/unmarshalers for all structs in a file - -build_tags string - build tags to add to generated file - -gen_build_flags string - build flags when running the generator while bootstrapping - -byte - use simple bytes instead of Base64Bytes for slice of bytes - -leave_temps - do not delete temporary files - -no_std_marshalers - don't generate MarshalJSON/UnmarshalJSON funcs - -noformat - do not run 'gofmt -w' on output file - -omit_empty - omit empty fields by default - -output_filename string - specify the filename of the output - -pkg - process the whole package instead of just the given file - -snake_case - use snake_case names instead of CamelCase by default - -lower_camel_case - use lowerCamelCase instead of CamelCase by default - -stubs - only generate stubs for marshaler/unmarshaler funcs - -disallow_unknown_fields - return error if some unknown field in json appeared - -disable_members_unescape - disable unescaping of \uXXXX string sequences in member names -``` - -Using `-all` will generate marshalers/unmarshalers for all Go structs in the -file excluding those structs whose preceding comment starts with `easyjson:skip`. -For example: - -```go -//easyjson:skip -type A struct {} -``` - -If `-all` is not provided, then only those structs whose preceding -comment starts with `easyjson:json` will have marshalers/unmarshalers -generated. For example: - -```go -//easyjson:json -type A struct {} -``` - -Additional option notes: - -* `-snake_case` tells easyjson to generate snake\_case field names by default - (unless overridden by a field tag). The CamelCase to snake\_case conversion - algorithm should work in most cases (ie, HTTPVersion will be converted to - "http_version"). - -* `-build_tags` will add the specified build tags to generated Go sources. - -* `-gen_build_flags` will execute the easyjson bootstapping code to launch the - actual generator command with provided flags. Multiple arguments should be - separated by space e.g. `-gen_build_flags="-mod=mod -x"`. - -## Structure json tag options - -Besides standard json tag options like 'omitempty' the following are supported: - -* 'nocopy' - disables allocation and copying of string values, making them - refer to original json buffer memory. This works great for short lived - objects which are not hold in memory after decoding and immediate usage. - Note if string requires unescaping it will be processed as normally. -* 'intern' - string "interning" (deduplication) to save memory when the very - same string dictionary values are often met all over the structure. - See below for more details. - -## Generated Marshaler/Unmarshaler Funcs - -For Go struct types, easyjson generates the funcs `MarshalEasyJSON` / -`UnmarshalEasyJSON` for marshaling/unmarshaling JSON. In turn, these satisfy -the `easyjson.Marshaler` and `easyjson.Unmarshaler` interfaces and when used in -conjunction with `easyjson.Marshal` / `easyjson.Unmarshal` avoid unnecessary -reflection / type assertions during marshaling/unmarshaling to/from JSON for Go -structs. - -easyjson also generates `MarshalJSON` and `UnmarshalJSON` funcs for Go struct -types compatible with the standard `json.Marshaler` and `json.Unmarshaler` -interfaces. Please be aware that using the standard `json.Marshal` / -`json.Unmarshal` for marshaling/unmarshaling will incur a significant -performance penalty when compared to using `easyjson.Marshal` / -`easyjson.Unmarshal`. - -Additionally, easyjson exposes utility funcs that use the `MarshalEasyJSON` and -`UnmarshalEasyJSON` for marshaling/unmarshaling to and from standard readers -and writers. For example, easyjson provides `easyjson.MarshalToHTTPResponseWriter` -which marshals to the standard `http.ResponseWriter`. Please see the [GoDoc -listing](https://godoc.org/github.com/mailru/easyjson) for the full listing of -utility funcs that are available. - -## Controlling easyjson Marshaling and Unmarshaling Behavior - -Go types can provide their own `MarshalEasyJSON` and `UnmarshalEasyJSON` funcs -that satisfy the `easyjson.Marshaler` / `easyjson.Unmarshaler` interfaces. -These will be used by `easyjson.Marshal` and `easyjson.Unmarshal` when defined -for a Go type. - -Go types can also satisfy the `easyjson.Optional` interface, which allows the -type to define its own `omitempty` logic. - -## Type Wrappers - -easyjson provides additional type wrappers defined in the `easyjson/opt` -package. These wrap the standard Go primitives and in turn satisfy the -easyjson interfaces. - -The `easyjson/opt` type wrappers are useful when needing to distinguish between -a missing value and/or when needing to specifying a default value. Type -wrappers allow easyjson to avoid additional pointers and heap allocations and -can significantly increase performance when used properly. - -## Memory Pooling - -easyjson uses a buffer pool that allocates data in increasing chunks from 128 -to 32768 bytes. Chunks of 512 bytes and larger will be reused with the help of -`sync.Pool`. The maximum size of a chunk is bounded to reduce redundant memory -allocation and to allow larger reusable buffers. - -easyjson's custom allocation buffer pool is defined in the `easyjson/buffer` -package, and the default behavior pool behavior can be modified (if necessary) -through a call to `buffer.Init()` prior to any marshaling or unmarshaling. -Please see the [GoDoc listing](https://godoc.org/github.com/mailru/easyjson/buffer) -for more information. - -## String interning - -During unmarshaling, `string` field values can be optionally -[interned](https://en.wikipedia.org/wiki/String_interning) to reduce memory -allocations and usage by deduplicating strings in memory, at the expense of slightly -increased CPU usage. - -This will work effectively only for `string` fields being decoded that have frequently -the same value (e.g. if you have a string field that can only assume a small number -of possible values). - -To enable string interning, add the `intern` keyword tag to your `json` tag on `string` -fields, e.g.: - -```go -type Foo struct { - UUID string `json:"uuid"` // will not be interned during unmarshaling - State string `json:"state,intern"` // will be interned during unmarshaling -} -``` - -## Issues, Notes, and Limitations - -* easyjson is still early in its development. As such, there are likely to be - bugs and missing features when compared to `encoding/json`. In the case of a - missing feature or bug, please create a GitHub issue. Pull requests are - welcome! - -* Unlike `encoding/json`, object keys are case-sensitive. Case-insensitive - matching is not currently provided due to the significant performance hit - when doing case-insensitive key matching. In the future, case-insensitive - object key matching may be provided via an option to the generator. - -* easyjson makes use of `unsafe`, which simplifies the code and - provides significant performance benefits by allowing no-copy - conversion from `[]byte` to `string`. That said, `unsafe` is used - only when unmarshaling and parsing JSON, and any `unsafe` operations - / memory allocations done will be safely deallocated by - easyjson. Set the build tag `easyjson_nounsafe` to compile it - without `unsafe`. - -* easyjson is compatible with Google App Engine. The `appengine` build - tag (set by App Engine's environment) will automatically disable the - use of `unsafe`, which is not allowed in App Engine's Standard - Environment. Note that the use with App Engine is still experimental. - -* Floats are formatted using the default precision from Go's `strconv` package. - As such, easyjson will not correctly handle high precision floats when - marshaling/unmarshaling JSON. Note, however, that there are very few/limited - uses where this behavior is not sufficient for general use. That said, a - different package may be needed if precise marshaling/unmarshaling of high - precision floats to/from JSON is required. - -* While unmarshaling, the JSON parser does the minimal amount of work needed to - skip over unmatching parens, and as such full validation is not done for the - entire JSON value being unmarshaled/parsed. - -* Currently there is no true streaming support for encoding/decoding as - typically for many uses/protocols the final, marshaled length of the JSON - needs to be known prior to sending the data. Currently this is not possible - with easyjson's architecture. - -* easyjson parser and codegen based on reflection, so it won't work on `package main` - files, because they cant be imported by parser. - -## Benchmarks - -Most benchmarks were done using the example -[13kB example JSON](https://dev.twitter.com/rest/reference/get/search/tweets) -(9k after eliminating whitespace). This example is similar to real-world data, -is well-structured, and contains a healthy variety of different types, making -it ideal for JSON serialization benchmarks. - -Note: - -* For small request benchmarks, an 80 byte portion of the above example was - used. - -* For large request marshaling benchmarks, a struct containing 50 regular - samples was used, making a ~500kB output JSON. - -* Benchmarks are showing the results of easyjson's default behaviour, - which makes use of `unsafe`. - -Benchmarks are available in the repository and can be run by invoking `make`. - -### easyjson vs. encoding/json - -easyjson is roughly 5-6 times faster than the standard `encoding/json` for -unmarshaling, and 3-4 times faster for non-concurrent marshaling. Concurrent -marshaling is 6-7x faster if marshaling to a writer. - -### easyjson vs. ffjson - -easyjson uses the same approach for JSON marshaling as -[ffjson](https://github.com/pquerna/ffjson), but takes a significantly -different approach to lexing and parsing JSON during unmarshaling. This means -easyjson is roughly 2-3x faster for unmarshaling and 1.5-2x faster for -non-concurrent unmarshaling. - -As of this writing, `ffjson` seems to have issues when used concurrently: -specifically, large request pooling hurts `ffjson`'s performance and causes -scalability issues. These issues with `ffjson` can likely be fixed, but as of -writing remain outstanding/known issues with `ffjson`. - -easyjson and `ffjson` have similar performance for small requests, however -easyjson outperforms `ffjson` by roughly 2-5x times for large requests when -used with a writer. - -### easyjson vs. go/codec - -[go/codec](https://github.com/ugorji/go) provides -compile-time helpers for JSON generation. In this case, helpers do not work -like marshalers as they are encoding-independent. - -easyjson is generally 2x faster than `go/codec` for non-concurrent benchmarks -and about 3x faster for concurrent encoding (without marshaling to a writer). - -In an attempt to measure marshaling performance of `go/codec` (as opposed to -allocations/memcpy/writer interface invocations), a benchmark was done with -resetting length of a byte slice rather than resetting the whole slice to nil. -However, the optimization in this exact form may not be applicable in practice, -since the memory is not freed between marshaling operations. - -### easyjson vs 'ujson' python module - -[ujson](https://github.com/esnme/ultrajson) is using C code for parsing, so it -is interesting to see how plain golang compares to that. It is important to note -that the resulting object for python is slower to access, since the library -parses JSON object into dictionaries. - -easyjson is slightly faster for unmarshaling and 2-3x faster than `ujson` for -marshaling. - -### Benchmark Results - -`ffjson` results are from February 4th, 2016, using the latest `ffjson` and go1.6. -`go/codec` results are from March 4th, 2016, using the latest `go/codec` and go1.6. - -#### Unmarshaling - -| lib | json size | MB/s | allocs/op | B/op | -|:---------|:----------|-----:|----------:|------:| -| standard | regular | 22 | 218 | 10229 | -| standard | small | 9.7 | 14 | 720 | -| | | | | | -| easyjson | regular | 125 | 128 | 9794 | -| easyjson | small | 67 | 3 | 128 | -| | | | | | -| ffjson | regular | 66 | 141 | 9985 | -| ffjson | small | 17.6 | 10 | 488 | -| | | | | | -| codec | regular | 55 | 434 | 19299 | -| codec | small | 29 | 7 | 336 | -| | | | | | -| ujson | regular | 103 | N/A | N/A | - -#### Marshaling, one goroutine. - -| lib | json size | MB/s | allocs/op | B/op | -|:----------|:----------|-----:|----------:|------:| -| standard | regular | 75 | 9 | 23256 | -| standard | small | 32 | 3 | 328 | -| standard | large | 80 | 17 | 1.2M | -| | | | | | -| easyjson | regular | 213 | 9 | 10260 | -| easyjson* | regular | 263 | 8 | 742 | -| easyjson | small | 125 | 1 | 128 | -| easyjson | large | 212 | 33 | 490k | -| easyjson* | large | 262 | 25 | 2879 | -| | | | | | -| ffjson | regular | 122 | 153 | 21340 | -| ffjson** | regular | 146 | 152 | 4897 | -| ffjson | small | 36 | 5 | 384 | -| ffjson** | small | 64 | 4 | 128 | -| ffjson | large | 134 | 7317 | 818k | -| ffjson** | large | 125 | 7320 | 827k | -| | | | | | -| codec | regular | 80 | 17 | 33601 | -| codec*** | regular | 108 | 9 | 1153 | -| codec | small | 42 | 3 | 304 | -| codec*** | small | 56 | 1 | 48 | -| codec | large | 73 | 483 | 2.5M | -| codec*** | large | 103 | 451 | 66007 | -| | | | | | -| ujson | regular | 92 | N/A | N/A | - -\* marshaling to a writer, -\*\* using `ffjson.Pool()`, -\*\*\* reusing output slice instead of resetting it to nil - -#### Marshaling, concurrent. - -| lib | json size | MB/s | allocs/op | B/op | -|:----------|:----------|-----:|----------:|------:| -| standard | regular | 252 | 9 | 23257 | -| standard | small | 124 | 3 | 328 | -| standard | large | 289 | 17 | 1.2M | -| | | | | | -| easyjson | regular | 792 | 9 | 10597 | -| easyjson* | regular | 1748 | 8 | 779 | -| easyjson | small | 333 | 1 | 128 | -| easyjson | large | 718 | 36 | 548k | -| easyjson* | large | 2134 | 25 | 4957 | -| | | | | | -| ffjson | regular | 301 | 153 | 21629 | -| ffjson** | regular | 707 | 152 | 5148 | -| ffjson | small | 62 | 5 | 384 | -| ffjson** | small | 282 | 4 | 128 | -| ffjson | large | 438 | 7330 | 1.0M | -| ffjson** | large | 131 | 7319 | 820k | -| | | | | | -| codec | regular | 183 | 17 | 33603 | -| codec*** | regular | 671 | 9 | 1157 | -| codec | small | 147 | 3 | 304 | -| codec*** | small | 299 | 1 | 48 | -| codec | large | 190 | 483 | 2.5M | -| codec*** | large | 752 | 451 | 77574 | - -\* marshaling to a writer, -\*\* using `ffjson.Pool()`, -\*\*\* reusing output slice instead of resetting it to nil diff --git a/vendor/github.com/mailru/easyjson/buffer/pool.go b/vendor/github.com/mailru/easyjson/buffer/pool.go deleted file mode 100644 index 598a54af9..000000000 --- a/vendor/github.com/mailru/easyjson/buffer/pool.go +++ /dev/null @@ -1,278 +0,0 @@ -// Package buffer implements a buffer for serialization, consisting of a chain of []byte-s to -// reduce copying and to allow reuse of individual chunks. -package buffer - -import ( - "io" - "net" - "sync" -) - -// PoolConfig contains configuration for the allocation and reuse strategy. -type PoolConfig struct { - StartSize int // Minimum chunk size that is allocated. - PooledSize int // Minimum chunk size that is reused, reusing chunks too small will result in overhead. - MaxSize int // Maximum chunk size that will be allocated. -} - -var config = PoolConfig{ - StartSize: 128, - PooledSize: 512, - MaxSize: 32768, -} - -// Reuse pool: chunk size -> pool. -var buffers = map[int]*sync.Pool{} - -func initBuffers() { - for l := config.PooledSize; l <= config.MaxSize; l *= 2 { - buffers[l] = new(sync.Pool) - } -} - -func init() { - initBuffers() -} - -// Init sets up a non-default pooling and allocation strategy. Should be run before serialization is done. -func Init(cfg PoolConfig) { - config = cfg - initBuffers() -} - -// putBuf puts a chunk to reuse pool if it can be reused. -func putBuf(buf []byte) { - size := cap(buf) - if size < config.PooledSize { - return - } - if c := buffers[size]; c != nil { - c.Put(buf[:0]) - } -} - -// getBuf gets a chunk from reuse pool or creates a new one if reuse failed. -func getBuf(size int) []byte { - if size >= config.PooledSize { - if c := buffers[size]; c != nil { - v := c.Get() - if v != nil { - return v.([]byte) - } - } - } - return make([]byte, 0, size) -} - -// Buffer is a buffer optimized for serialization without extra copying. -type Buffer struct { - - // Buf is the current chunk that can be used for serialization. - Buf []byte - - toPool []byte - bufs [][]byte -} - -// EnsureSpace makes sure that the current chunk contains at least s free bytes, -// possibly creating a new chunk. -func (b *Buffer) EnsureSpace(s int) { - if cap(b.Buf)-len(b.Buf) < s { - b.ensureSpaceSlow(s) - } -} - -func (b *Buffer) ensureSpaceSlow(s int) { - l := len(b.Buf) - if l > 0 { - if cap(b.toPool) != cap(b.Buf) { - // Chunk was reallocated, toPool can be pooled. - putBuf(b.toPool) - } - if cap(b.bufs) == 0 { - b.bufs = make([][]byte, 0, 8) - } - b.bufs = append(b.bufs, b.Buf) - l = cap(b.toPool) * 2 - } else { - l = config.StartSize - } - - if l > config.MaxSize { - l = config.MaxSize - } - b.Buf = getBuf(l) - b.toPool = b.Buf -} - -// AppendByte appends a single byte to buffer. -func (b *Buffer) AppendByte(data byte) { - b.EnsureSpace(1) - b.Buf = append(b.Buf, data) -} - -// AppendBytes appends a byte slice to buffer. -func (b *Buffer) AppendBytes(data []byte) { - if len(data) <= cap(b.Buf)-len(b.Buf) { - b.Buf = append(b.Buf, data...) // fast path - } else { - b.appendBytesSlow(data) - } -} - -func (b *Buffer) appendBytesSlow(data []byte) { - for len(data) > 0 { - b.EnsureSpace(1) - - sz := cap(b.Buf) - len(b.Buf) - if sz > len(data) { - sz = len(data) - } - - b.Buf = append(b.Buf, data[:sz]...) - data = data[sz:] - } -} - -// AppendString appends a string to buffer. -func (b *Buffer) AppendString(data string) { - if len(data) <= cap(b.Buf)-len(b.Buf) { - b.Buf = append(b.Buf, data...) // fast path - } else { - b.appendStringSlow(data) - } -} - -func (b *Buffer) appendStringSlow(data string) { - for len(data) > 0 { - b.EnsureSpace(1) - - sz := cap(b.Buf) - len(b.Buf) - if sz > len(data) { - sz = len(data) - } - - b.Buf = append(b.Buf, data[:sz]...) - data = data[sz:] - } -} - -// Size computes the size of a buffer by adding sizes of every chunk. -func (b *Buffer) Size() int { - size := len(b.Buf) - for _, buf := range b.bufs { - size += len(buf) - } - return size -} - -// DumpTo outputs the contents of a buffer to a writer and resets the buffer. -func (b *Buffer) DumpTo(w io.Writer) (written int, err error) { - bufs := net.Buffers(b.bufs) - if len(b.Buf) > 0 { - bufs = append(bufs, b.Buf) - } - n, err := bufs.WriteTo(w) - - for _, buf := range b.bufs { - putBuf(buf) - } - putBuf(b.toPool) - - b.bufs = nil - b.Buf = nil - b.toPool = nil - - return int(n), err -} - -// BuildBytes creates a single byte slice with all the contents of the buffer. Data is -// copied if it does not fit in a single chunk. You can optionally provide one byte -// slice as argument that it will try to reuse. -func (b *Buffer) BuildBytes(reuse ...[]byte) []byte { - if len(b.bufs) == 0 { - ret := b.Buf - b.toPool = nil - b.Buf = nil - return ret - } - - var ret []byte - size := b.Size() - - // If we got a buffer as argument and it is big enough, reuse it. - if len(reuse) == 1 && cap(reuse[0]) >= size { - ret = reuse[0][:0] - } else { - ret = make([]byte, 0, size) - } - for _, buf := range b.bufs { - ret = append(ret, buf...) - putBuf(buf) - } - - ret = append(ret, b.Buf...) - putBuf(b.toPool) - - b.bufs = nil - b.toPool = nil - b.Buf = nil - - return ret -} - -type readCloser struct { - offset int - bufs [][]byte -} - -func (r *readCloser) Read(p []byte) (n int, err error) { - for _, buf := range r.bufs { - // Copy as much as we can. - x := copy(p[n:], buf[r.offset:]) - n += x // Increment how much we filled. - - // Did we empty the whole buffer? - if r.offset+x == len(buf) { - // On to the next buffer. - r.offset = 0 - r.bufs = r.bufs[1:] - - // We can release this buffer. - putBuf(buf) - } else { - r.offset += x - } - - if n == len(p) { - break - } - } - // No buffers left or nothing read? - if len(r.bufs) == 0 { - err = io.EOF - } - return -} - -func (r *readCloser) Close() error { - // Release all remaining buffers. - for _, buf := range r.bufs { - putBuf(buf) - } - // In case Close gets called multiple times. - r.bufs = nil - - return nil -} - -// ReadCloser creates an io.ReadCloser with all the contents of the buffer. -func (b *Buffer) ReadCloser() io.ReadCloser { - ret := &readCloser{0, append(b.bufs, b.Buf)} - - b.bufs = nil - b.toPool = nil - b.Buf = nil - - return ret -} diff --git a/vendor/github.com/mailru/easyjson/helpers.go b/vendor/github.com/mailru/easyjson/helpers.go deleted file mode 100644 index efe34bf2a..000000000 --- a/vendor/github.com/mailru/easyjson/helpers.go +++ /dev/null @@ -1,114 +0,0 @@ -// Package easyjson contains marshaler/unmarshaler interfaces and helper functions. -package easyjson - -import ( - "io" - "io/ioutil" - "net/http" - "strconv" - "unsafe" - - "github.com/mailru/easyjson/jlexer" - "github.com/mailru/easyjson/jwriter" -) - -// Marshaler is an easyjson-compatible marshaler interface. -type Marshaler interface { - MarshalEasyJSON(w *jwriter.Writer) -} - -// Unmarshaler is an easyjson-compatible unmarshaler interface. -type Unmarshaler interface { - UnmarshalEasyJSON(w *jlexer.Lexer) -} - -// MarshalerUnmarshaler is an easyjson-compatible marshaler/unmarshaler interface. -type MarshalerUnmarshaler interface { - Marshaler - Unmarshaler -} - -// Optional defines an undefined-test method for a type to integrate with 'omitempty' logic. -type Optional interface { - IsDefined() bool -} - -// UnknownsUnmarshaler provides a method to unmarshal unknown struct fileds and save them as you want -type UnknownsUnmarshaler interface { - UnmarshalUnknown(in *jlexer.Lexer, key string) -} - -// UnknownsMarshaler provides a method to write additional struct fields -type UnknownsMarshaler interface { - MarshalUnknowns(w *jwriter.Writer, first bool) -} - -func isNilInterface(i interface{}) bool { - return (*[2]uintptr)(unsafe.Pointer(&i))[1] == 0 -} - -// Marshal returns data as a single byte slice. Method is suboptimal as the data is likely to be copied -// from a chain of smaller chunks. -func Marshal(v Marshaler) ([]byte, error) { - if isNilInterface(v) { - return nullBytes, nil - } - - w := jwriter.Writer{} - v.MarshalEasyJSON(&w) - return w.BuildBytes() -} - -// MarshalToWriter marshals the data to an io.Writer. -func MarshalToWriter(v Marshaler, w io.Writer) (written int, err error) { - if isNilInterface(v) { - return w.Write(nullBytes) - } - - jw := jwriter.Writer{} - v.MarshalEasyJSON(&jw) - return jw.DumpTo(w) -} - -// MarshalToHTTPResponseWriter sets Content-Length and Content-Type headers for the -// http.ResponseWriter, and send the data to the writer. started will be equal to -// false if an error occurred before any http.ResponseWriter methods were actually -// invoked (in this case a 500 reply is possible). -func MarshalToHTTPResponseWriter(v Marshaler, w http.ResponseWriter) (started bool, written int, err error) { - if isNilInterface(v) { - w.Header().Set("Content-Type", "application/json") - w.Header().Set("Content-Length", strconv.Itoa(len(nullBytes))) - written, err = w.Write(nullBytes) - return true, written, err - } - - jw := jwriter.Writer{} - v.MarshalEasyJSON(&jw) - if jw.Error != nil { - return false, 0, jw.Error - } - w.Header().Set("Content-Type", "application/json") - w.Header().Set("Content-Length", strconv.Itoa(jw.Size())) - - started = true - written, err = jw.DumpTo(w) - return -} - -// Unmarshal decodes the JSON in data into the object. -func Unmarshal(data []byte, v Unmarshaler) error { - l := jlexer.Lexer{Data: data} - v.UnmarshalEasyJSON(&l) - return l.Error() -} - -// UnmarshalFromReader reads all the data in the reader and decodes as JSON into the object. -func UnmarshalFromReader(r io.Reader, v Unmarshaler) error { - data, err := ioutil.ReadAll(r) - if err != nil { - return err - } - l := jlexer.Lexer{Data: data} - v.UnmarshalEasyJSON(&l) - return l.Error() -} diff --git a/vendor/github.com/mailru/easyjson/jlexer/bytestostr.go b/vendor/github.com/mailru/easyjson/jlexer/bytestostr.go deleted file mode 100644 index e68108f86..000000000 --- a/vendor/github.com/mailru/easyjson/jlexer/bytestostr.go +++ /dev/null @@ -1,21 +0,0 @@ -// This file will only be included to the build if neither -// easyjson_nounsafe nor appengine build tag is set. See README notes -// for more details. - -//+build !easyjson_nounsafe -//+build !appengine - -package jlexer - -import ( - "unsafe" -) - -// bytesToStr creates a string pointing at the slice to avoid copying. -// -// Warning: the string returned by the function should be used with care, as the whole input data -// chunk may be either blocked from being freed by GC because of a single string or the buffer.Data -// may be garbage-collected even when the string exists. -func bytesToStr(data []byte) string { - return *(*string)(unsafe.Pointer(&data)) -} diff --git a/vendor/github.com/mailru/easyjson/jlexer/bytestostr_nounsafe.go b/vendor/github.com/mailru/easyjson/jlexer/bytestostr_nounsafe.go deleted file mode 100644 index 864d1be67..000000000 --- a/vendor/github.com/mailru/easyjson/jlexer/bytestostr_nounsafe.go +++ /dev/null @@ -1,13 +0,0 @@ -// This file is included to the build if any of the buildtags below -// are defined. Refer to README notes for more details. - -//+build easyjson_nounsafe appengine - -package jlexer - -// bytesToStr creates a string normally from []byte -// -// Note that this method is roughly 1.5x slower than using the 'unsafe' method. -func bytesToStr(data []byte) string { - return string(data) -} diff --git a/vendor/github.com/mailru/easyjson/jlexer/error.go b/vendor/github.com/mailru/easyjson/jlexer/error.go deleted file mode 100644 index e90ec40d0..000000000 --- a/vendor/github.com/mailru/easyjson/jlexer/error.go +++ /dev/null @@ -1,15 +0,0 @@ -package jlexer - -import "fmt" - -// LexerError implements the error interface and represents all possible errors that can be -// generated during parsing the JSON data. -type LexerError struct { - Reason string - Offset int - Data string -} - -func (l *LexerError) Error() string { - return fmt.Sprintf("parse error: %s near offset %d of '%s'", l.Reason, l.Offset, l.Data) -} diff --git a/vendor/github.com/mailru/easyjson/jlexer/lexer.go b/vendor/github.com/mailru/easyjson/jlexer/lexer.go deleted file mode 100644 index a27705b12..000000000 --- a/vendor/github.com/mailru/easyjson/jlexer/lexer.go +++ /dev/null @@ -1,1257 +0,0 @@ -// Package jlexer contains a JSON lexer implementation. -// -// It is expected that it is mostly used with generated parser code, so the interface is tuned -// for a parser that knows what kind of data is expected. -package jlexer - -import ( - "bytes" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "io" - "strconv" - "unicode" - "unicode/utf16" - "unicode/utf8" - - "github.com/josharian/intern" -) - -// TokenKind determines type of a token. -type TokenKind byte - -const ( - TokenUndef TokenKind = iota // No token. - TokenDelim // Delimiter: one of '{', '}', '[' or ']'. - TokenString // A string literal, e.g. "abc\u1234" - TokenNumber // Number literal, e.g. 1.5e5 - TokenBool // Boolean literal: true or false. - TokenNull // null keyword. -) - -// token describes a single token: type, position in the input and value. -type token struct { - kind TokenKind // Type of a token. - - boolValue bool // Value if a boolean literal token. - byteValueCloned bool // true if byteValue was allocated and does not refer to original json body - byteValue []byte // Raw value of a token. - delimValue byte -} - -// Lexer is a JSON lexer: it iterates over JSON tokens in a byte slice. -type Lexer struct { - Data []byte // Input data given to the lexer. - - start int // Start of the current token. - pos int // Current unscanned position in the input stream. - token token // Last scanned token, if token.kind != TokenUndef. - - firstElement bool // Whether current element is the first in array or an object. - wantSep byte // A comma or a colon character, which need to occur before a token. - - UseMultipleErrors bool // If we want to use multiple errors. - fatalError error // Fatal error occurred during lexing. It is usually a syntax error. - multipleErrors []*LexerError // Semantic errors occurred during lexing. Marshalling will be continued after finding this errors. -} - -// FetchToken scans the input for the next token. -func (r *Lexer) FetchToken() { - r.token.kind = TokenUndef - r.start = r.pos - - // Check if r.Data has r.pos element - // If it doesn't, it mean corrupted input data - if len(r.Data) < r.pos { - r.errParse("Unexpected end of data") - return - } - // Determine the type of a token by skipping whitespace and reading the - // first character. - for _, c := range r.Data[r.pos:] { - switch c { - case ':', ',': - if r.wantSep == c { - r.pos++ - r.start++ - r.wantSep = 0 - } else { - r.errSyntax() - } - - case ' ', '\t', '\r', '\n': - r.pos++ - r.start++ - - case '"': - if r.wantSep != 0 { - r.errSyntax() - } - - r.token.kind = TokenString - r.fetchString() - return - - case '{', '[': - if r.wantSep != 0 { - r.errSyntax() - } - r.firstElement = true - r.token.kind = TokenDelim - r.token.delimValue = r.Data[r.pos] - r.pos++ - return - - case '}', ']': - if !r.firstElement && (r.wantSep != ',') { - r.errSyntax() - } - r.wantSep = 0 - r.token.kind = TokenDelim - r.token.delimValue = r.Data[r.pos] - r.pos++ - return - - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-': - if r.wantSep != 0 { - r.errSyntax() - } - r.token.kind = TokenNumber - r.fetchNumber() - return - - case 'n': - if r.wantSep != 0 { - r.errSyntax() - } - - r.token.kind = TokenNull - r.fetchNull() - return - - case 't': - if r.wantSep != 0 { - r.errSyntax() - } - - r.token.kind = TokenBool - r.token.boolValue = true - r.fetchTrue() - return - - case 'f': - if r.wantSep != 0 { - r.errSyntax() - } - - r.token.kind = TokenBool - r.token.boolValue = false - r.fetchFalse() - return - - default: - r.errSyntax() - return - } - } - r.fatalError = io.EOF - return -} - -// isTokenEnd returns true if the char can follow a non-delimiter token -func isTokenEnd(c byte) bool { - return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '[' || c == ']' || c == '{' || c == '}' || c == ',' || c == ':' -} - -// fetchNull fetches and checks remaining bytes of null keyword. -func (r *Lexer) fetchNull() { - r.pos += 4 - if r.pos > len(r.Data) || - r.Data[r.pos-3] != 'u' || - r.Data[r.pos-2] != 'l' || - r.Data[r.pos-1] != 'l' || - (r.pos != len(r.Data) && !isTokenEnd(r.Data[r.pos])) { - - r.pos -= 4 - r.errSyntax() - } -} - -// fetchTrue fetches and checks remaining bytes of true keyword. -func (r *Lexer) fetchTrue() { - r.pos += 4 - if r.pos > len(r.Data) || - r.Data[r.pos-3] != 'r' || - r.Data[r.pos-2] != 'u' || - r.Data[r.pos-1] != 'e' || - (r.pos != len(r.Data) && !isTokenEnd(r.Data[r.pos])) { - - r.pos -= 4 - r.errSyntax() - } -} - -// fetchFalse fetches and checks remaining bytes of false keyword. -func (r *Lexer) fetchFalse() { - r.pos += 5 - if r.pos > len(r.Data) || - r.Data[r.pos-4] != 'a' || - r.Data[r.pos-3] != 'l' || - r.Data[r.pos-2] != 's' || - r.Data[r.pos-1] != 'e' || - (r.pos != len(r.Data) && !isTokenEnd(r.Data[r.pos])) { - - r.pos -= 5 - r.errSyntax() - } -} - -// fetchNumber scans a number literal token. -func (r *Lexer) fetchNumber() { - hasE := false - afterE := false - hasDot := false - - r.pos++ - for i, c := range r.Data[r.pos:] { - switch { - case c >= '0' && c <= '9': - afterE = false - case c == '.' && !hasDot: - hasDot = true - case (c == 'e' || c == 'E') && !hasE: - hasE = true - hasDot = true - afterE = true - case (c == '+' || c == '-') && afterE: - afterE = false - default: - r.pos += i - if !isTokenEnd(c) { - r.errSyntax() - } else { - r.token.byteValue = r.Data[r.start:r.pos] - } - return - } - } - - r.pos = len(r.Data) - r.token.byteValue = r.Data[r.start:] -} - -// findStringLen tries to scan into the string literal for ending quote char to determine required size. -// The size will be exact if no escapes are present and may be inexact if there are escaped chars. -func findStringLen(data []byte) (isValid bool, length int) { - for { - idx := bytes.IndexByte(data, '"') - if idx == -1 { - return false, len(data) - } - if idx == 0 || (idx > 0 && data[idx-1] != '\\') { - return true, length + idx - } - - // count \\\\\\\ sequences. even number of slashes means quote is not really escaped - cnt := 1 - for idx-cnt-1 >= 0 && data[idx-cnt-1] == '\\' { - cnt++ - } - if cnt%2 == 0 { - return true, length + idx - } - - length += idx + 1 - data = data[idx+1:] - } -} - -// unescapeStringToken performs unescaping of string token. -// if no escaping is needed, original string is returned, otherwise - a new one allocated -func (r *Lexer) unescapeStringToken() (err error) { - data := r.token.byteValue - var unescapedData []byte - - for { - i := bytes.IndexByte(data, '\\') - if i == -1 { - break - } - - escapedRune, escapedBytes, err := decodeEscape(data[i:]) - if err != nil { - r.errParse(err.Error()) - return err - } - - if unescapedData == nil { - unescapedData = make([]byte, 0, len(r.token.byteValue)) - } - - var d [4]byte - s := utf8.EncodeRune(d[:], escapedRune) - unescapedData = append(unescapedData, data[:i]...) - unescapedData = append(unescapedData, d[:s]...) - - data = data[i+escapedBytes:] - } - - if unescapedData != nil { - r.token.byteValue = append(unescapedData, data...) - r.token.byteValueCloned = true - } - return -} - -// getu4 decodes \uXXXX from the beginning of s, returning the hex value, -// or it returns -1. -func getu4(s []byte) rune { - if len(s) < 6 || s[0] != '\\' || s[1] != 'u' { - return -1 - } - var val rune - for i := 2; i < len(s) && i < 6; i++ { - var v byte - c := s[i] - switch c { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - v = c - '0' - case 'a', 'b', 'c', 'd', 'e', 'f': - v = c - 'a' + 10 - case 'A', 'B', 'C', 'D', 'E', 'F': - v = c - 'A' + 10 - default: - return -1 - } - - val <<= 4 - val |= rune(v) - } - return val -} - -// decodeEscape processes a single escape sequence and returns number of bytes processed. -func decodeEscape(data []byte) (decoded rune, bytesProcessed int, err error) { - if len(data) < 2 { - return 0, 0, errors.New("incorrect escape symbol \\ at the end of token") - } - - c := data[1] - switch c { - case '"', '/', '\\': - return rune(c), 2, nil - case 'b': - return '\b', 2, nil - case 'f': - return '\f', 2, nil - case 'n': - return '\n', 2, nil - case 'r': - return '\r', 2, nil - case 't': - return '\t', 2, nil - case 'u': - rr := getu4(data) - if rr < 0 { - return 0, 0, errors.New("incorrectly escaped \\uXXXX sequence") - } - - read := 6 - if utf16.IsSurrogate(rr) { - rr1 := getu4(data[read:]) - if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar { - read += 6 - rr = dec - } else { - rr = unicode.ReplacementChar - } - } - return rr, read, nil - } - - return 0, 0, errors.New("incorrectly escaped bytes") -} - -// fetchString scans a string literal token. -func (r *Lexer) fetchString() { - r.pos++ - data := r.Data[r.pos:] - - isValid, length := findStringLen(data) - if !isValid { - r.pos += length - r.errParse("unterminated string literal") - return - } - r.token.byteValue = data[:length] - r.pos += length + 1 // skip closing '"' as well -} - -// scanToken scans the next token if no token is currently available in the lexer. -func (r *Lexer) scanToken() { - if r.token.kind != TokenUndef || r.fatalError != nil { - return - } - - r.FetchToken() -} - -// consume resets the current token to allow scanning the next one. -func (r *Lexer) consume() { - r.token.kind = TokenUndef - r.token.byteValueCloned = false - r.token.delimValue = 0 -} - -// Ok returns true if no error (including io.EOF) was encountered during scanning. -func (r *Lexer) Ok() bool { - return r.fatalError == nil -} - -const maxErrorContextLen = 13 - -func (r *Lexer) errParse(what string) { - if r.fatalError == nil { - var str string - if len(r.Data)-r.pos <= maxErrorContextLen { - str = string(r.Data) - } else { - str = string(r.Data[r.pos:r.pos+maxErrorContextLen-3]) + "..." - } - r.fatalError = &LexerError{ - Reason: what, - Offset: r.pos, - Data: str, - } - } -} - -func (r *Lexer) errSyntax() { - r.errParse("syntax error") -} - -func (r *Lexer) errInvalidToken(expected string) { - if r.fatalError != nil { - return - } - if r.UseMultipleErrors { - r.pos = r.start - r.consume() - r.SkipRecursive() - switch expected { - case "[": - r.token.delimValue = ']' - r.token.kind = TokenDelim - case "{": - r.token.delimValue = '}' - r.token.kind = TokenDelim - } - r.addNonfatalError(&LexerError{ - Reason: fmt.Sprintf("expected %s", expected), - Offset: r.start, - Data: string(r.Data[r.start:r.pos]), - }) - return - } - - var str string - if len(r.token.byteValue) <= maxErrorContextLen { - str = string(r.token.byteValue) - } else { - str = string(r.token.byteValue[:maxErrorContextLen-3]) + "..." - } - r.fatalError = &LexerError{ - Reason: fmt.Sprintf("expected %s", expected), - Offset: r.pos, - Data: str, - } -} - -func (r *Lexer) GetPos() int { - return r.pos -} - -// Delim consumes a token and verifies that it is the given delimiter. -func (r *Lexer) Delim(c byte) { - if r.token.kind == TokenUndef && r.Ok() { - r.FetchToken() - } - - if !r.Ok() || r.token.delimValue != c { - r.consume() // errInvalidToken can change token if UseMultipleErrors is enabled. - r.errInvalidToken(string([]byte{c})) - } else { - r.consume() - } -} - -// IsDelim returns true if there was no scanning error and next token is the given delimiter. -func (r *Lexer) IsDelim(c byte) bool { - if r.token.kind == TokenUndef && r.Ok() { - r.FetchToken() - } - return !r.Ok() || r.token.delimValue == c -} - -// Null verifies that the next token is null and consumes it. -func (r *Lexer) Null() { - if r.token.kind == TokenUndef && r.Ok() { - r.FetchToken() - } - if !r.Ok() || r.token.kind != TokenNull { - r.errInvalidToken("null") - } - r.consume() -} - -// IsNull returns true if the next token is a null keyword. -func (r *Lexer) IsNull() bool { - if r.token.kind == TokenUndef && r.Ok() { - r.FetchToken() - } - return r.Ok() && r.token.kind == TokenNull -} - -// Skip skips a single token. -func (r *Lexer) Skip() { - if r.token.kind == TokenUndef && r.Ok() { - r.FetchToken() - } - r.consume() -} - -// SkipRecursive skips next array or object completely, or just skips a single token if not -// an array/object. -// -// Note: no syntax validation is performed on the skipped data. -func (r *Lexer) SkipRecursive() { - r.scanToken() - var start, end byte - startPos := r.start - - switch r.token.delimValue { - case '{': - start, end = '{', '}' - case '[': - start, end = '[', ']' - default: - r.consume() - return - } - - r.consume() - - level := 1 - inQuotes := false - wasEscape := false - - for i, c := range r.Data[r.pos:] { - switch { - case c == start && !inQuotes: - level++ - case c == end && !inQuotes: - level-- - if level == 0 { - r.pos += i + 1 - if !json.Valid(r.Data[startPos:r.pos]) { - r.pos = len(r.Data) - r.fatalError = &LexerError{ - Reason: "skipped array/object json value is invalid", - Offset: r.pos, - Data: string(r.Data[r.pos:]), - } - } - return - } - case c == '\\' && inQuotes: - wasEscape = !wasEscape - continue - case c == '"' && inQuotes: - inQuotes = wasEscape - case c == '"': - inQuotes = true - } - wasEscape = false - } - r.pos = len(r.Data) - r.fatalError = &LexerError{ - Reason: "EOF reached while skipping array/object or token", - Offset: r.pos, - Data: string(r.Data[r.pos:]), - } -} - -// Raw fetches the next item recursively as a data slice -func (r *Lexer) Raw() []byte { - r.SkipRecursive() - if !r.Ok() { - return nil - } - return r.Data[r.start:r.pos] -} - -// IsStart returns whether the lexer is positioned at the start -// of an input string. -func (r *Lexer) IsStart() bool { - return r.pos == 0 -} - -// Consumed reads all remaining bytes from the input, publishing an error if -// there is anything but whitespace remaining. -func (r *Lexer) Consumed() { - if r.pos > len(r.Data) || !r.Ok() { - return - } - - for _, c := range r.Data[r.pos:] { - if c != ' ' && c != '\t' && c != '\r' && c != '\n' { - r.AddError(&LexerError{ - Reason: "invalid character '" + string(c) + "' after top-level value", - Offset: r.pos, - Data: string(r.Data[r.pos:]), - }) - return - } - - r.pos++ - r.start++ - } -} - -func (r *Lexer) unsafeString(skipUnescape bool) (string, []byte) { - if r.token.kind == TokenUndef && r.Ok() { - r.FetchToken() - } - if !r.Ok() || r.token.kind != TokenString { - r.errInvalidToken("string") - return "", nil - } - if !skipUnescape { - if err := r.unescapeStringToken(); err != nil { - r.errInvalidToken("string") - return "", nil - } - } - - bytes := r.token.byteValue - ret := bytesToStr(r.token.byteValue) - r.consume() - return ret, bytes -} - -// UnsafeString returns the string value if the token is a string literal. -// -// Warning: returned string may point to the input buffer, so the string should not outlive -// the input buffer. Intended pattern of usage is as an argument to a switch statement. -func (r *Lexer) UnsafeString() string { - ret, _ := r.unsafeString(false) - return ret -} - -// UnsafeBytes returns the byte slice if the token is a string literal. -func (r *Lexer) UnsafeBytes() []byte { - _, ret := r.unsafeString(false) - return ret -} - -// UnsafeFieldName returns current member name string token -func (r *Lexer) UnsafeFieldName(skipUnescape bool) string { - ret, _ := r.unsafeString(skipUnescape) - return ret -} - -// String reads a string literal. -func (r *Lexer) String() string { - if r.token.kind == TokenUndef && r.Ok() { - r.FetchToken() - } - if !r.Ok() || r.token.kind != TokenString { - r.errInvalidToken("string") - return "" - } - if err := r.unescapeStringToken(); err != nil { - r.errInvalidToken("string") - return "" - } - var ret string - if r.token.byteValueCloned { - ret = bytesToStr(r.token.byteValue) - } else { - ret = string(r.token.byteValue) - } - r.consume() - return ret -} - -// StringIntern reads a string literal, and performs string interning on it. -func (r *Lexer) StringIntern() string { - if r.token.kind == TokenUndef && r.Ok() { - r.FetchToken() - } - if !r.Ok() || r.token.kind != TokenString { - r.errInvalidToken("string") - return "" - } - if err := r.unescapeStringToken(); err != nil { - r.errInvalidToken("string") - return "" - } - ret := intern.Bytes(r.token.byteValue) - r.consume() - return ret -} - -// Bytes reads a string literal and base64 decodes it into a byte slice. -func (r *Lexer) Bytes() []byte { - if r.token.kind == TokenUndef && r.Ok() { - r.FetchToken() - } - if !r.Ok() || r.token.kind != TokenString { - r.errInvalidToken("string") - return nil - } - if err := r.unescapeStringToken(); err != nil { - r.errInvalidToken("string") - return nil - } - ret := make([]byte, base64.StdEncoding.DecodedLen(len(r.token.byteValue))) - n, err := base64.StdEncoding.Decode(ret, r.token.byteValue) - if err != nil { - r.fatalError = &LexerError{ - Reason: err.Error(), - } - return nil - } - - r.consume() - return ret[:n] -} - -// Bool reads a true or false boolean keyword. -func (r *Lexer) Bool() bool { - if r.token.kind == TokenUndef && r.Ok() { - r.FetchToken() - } - if !r.Ok() || r.token.kind != TokenBool { - r.errInvalidToken("bool") - return false - } - ret := r.token.boolValue - r.consume() - return ret -} - -func (r *Lexer) number() string { - if r.token.kind == TokenUndef && r.Ok() { - r.FetchToken() - } - if !r.Ok() || r.token.kind != TokenNumber { - r.errInvalidToken("number") - return "" - } - ret := bytesToStr(r.token.byteValue) - r.consume() - return ret -} - -func (r *Lexer) Uint8() uint8 { - s := r.number() - if !r.Ok() { - return 0 - } - - n, err := strconv.ParseUint(s, 10, 8) - if err != nil { - r.addNonfatalError(&LexerError{ - Offset: r.start, - Reason: err.Error(), - Data: s, - }) - } - return uint8(n) -} - -func (r *Lexer) Uint16() uint16 { - s := r.number() - if !r.Ok() { - return 0 - } - - n, err := strconv.ParseUint(s, 10, 16) - if err != nil { - r.addNonfatalError(&LexerError{ - Offset: r.start, - Reason: err.Error(), - Data: s, - }) - } - return uint16(n) -} - -func (r *Lexer) Uint32() uint32 { - s := r.number() - if !r.Ok() { - return 0 - } - - n, err := strconv.ParseUint(s, 10, 32) - if err != nil { - r.addNonfatalError(&LexerError{ - Offset: r.start, - Reason: err.Error(), - Data: s, - }) - } - return uint32(n) -} - -func (r *Lexer) Uint64() uint64 { - s := r.number() - if !r.Ok() { - return 0 - } - - n, err := strconv.ParseUint(s, 10, 64) - if err != nil { - r.addNonfatalError(&LexerError{ - Offset: r.start, - Reason: err.Error(), - Data: s, - }) - } - return n -} - -func (r *Lexer) Uint() uint { - return uint(r.Uint64()) -} - -func (r *Lexer) Int8() int8 { - s := r.number() - if !r.Ok() { - return 0 - } - - n, err := strconv.ParseInt(s, 10, 8) - if err != nil { - r.addNonfatalError(&LexerError{ - Offset: r.start, - Reason: err.Error(), - Data: s, - }) - } - return int8(n) -} - -func (r *Lexer) Int16() int16 { - s := r.number() - if !r.Ok() { - return 0 - } - - n, err := strconv.ParseInt(s, 10, 16) - if err != nil { - r.addNonfatalError(&LexerError{ - Offset: r.start, - Reason: err.Error(), - Data: s, - }) - } - return int16(n) -} - -func (r *Lexer) Int32() int32 { - s := r.number() - if !r.Ok() { - return 0 - } - - n, err := strconv.ParseInt(s, 10, 32) - if err != nil { - r.addNonfatalError(&LexerError{ - Offset: r.start, - Reason: err.Error(), - Data: s, - }) - } - return int32(n) -} - -func (r *Lexer) Int64() int64 { - s := r.number() - if !r.Ok() { - return 0 - } - - n, err := strconv.ParseInt(s, 10, 64) - if err != nil { - r.addNonfatalError(&LexerError{ - Offset: r.start, - Reason: err.Error(), - Data: s, - }) - } - return n -} - -func (r *Lexer) Int() int { - return int(r.Int64()) -} - -func (r *Lexer) Uint8Str() uint8 { - s, b := r.unsafeString(false) - if !r.Ok() { - return 0 - } - - n, err := strconv.ParseUint(s, 10, 8) - if err != nil { - r.addNonfatalError(&LexerError{ - Offset: r.start, - Reason: err.Error(), - Data: string(b), - }) - } - return uint8(n) -} - -func (r *Lexer) Uint16Str() uint16 { - s, b := r.unsafeString(false) - if !r.Ok() { - return 0 - } - - n, err := strconv.ParseUint(s, 10, 16) - if err != nil { - r.addNonfatalError(&LexerError{ - Offset: r.start, - Reason: err.Error(), - Data: string(b), - }) - } - return uint16(n) -} - -func (r *Lexer) Uint32Str() uint32 { - s, b := r.unsafeString(false) - if !r.Ok() { - return 0 - } - - n, err := strconv.ParseUint(s, 10, 32) - if err != nil { - r.addNonfatalError(&LexerError{ - Offset: r.start, - Reason: err.Error(), - Data: string(b), - }) - } - return uint32(n) -} - -func (r *Lexer) Uint64Str() uint64 { - s, b := r.unsafeString(false) - if !r.Ok() { - return 0 - } - - n, err := strconv.ParseUint(s, 10, 64) - if err != nil { - r.addNonfatalError(&LexerError{ - Offset: r.start, - Reason: err.Error(), - Data: string(b), - }) - } - return n -} - -func (r *Lexer) UintStr() uint { - return uint(r.Uint64Str()) -} - -func (r *Lexer) UintptrStr() uintptr { - return uintptr(r.Uint64Str()) -} - -func (r *Lexer) Int8Str() int8 { - s, b := r.unsafeString(false) - if !r.Ok() { - return 0 - } - - n, err := strconv.ParseInt(s, 10, 8) - if err != nil { - r.addNonfatalError(&LexerError{ - Offset: r.start, - Reason: err.Error(), - Data: string(b), - }) - } - return int8(n) -} - -func (r *Lexer) Int16Str() int16 { - s, b := r.unsafeString(false) - if !r.Ok() { - return 0 - } - - n, err := strconv.ParseInt(s, 10, 16) - if err != nil { - r.addNonfatalError(&LexerError{ - Offset: r.start, - Reason: err.Error(), - Data: string(b), - }) - } - return int16(n) -} - -func (r *Lexer) Int32Str() int32 { - s, b := r.unsafeString(false) - if !r.Ok() { - return 0 - } - - n, err := strconv.ParseInt(s, 10, 32) - if err != nil { - r.addNonfatalError(&LexerError{ - Offset: r.start, - Reason: err.Error(), - Data: string(b), - }) - } - return int32(n) -} - -func (r *Lexer) Int64Str() int64 { - s, b := r.unsafeString(false) - if !r.Ok() { - return 0 - } - - n, err := strconv.ParseInt(s, 10, 64) - if err != nil { - r.addNonfatalError(&LexerError{ - Offset: r.start, - Reason: err.Error(), - Data: string(b), - }) - } - return n -} - -func (r *Lexer) IntStr() int { - return int(r.Int64Str()) -} - -func (r *Lexer) Float32() float32 { - s := r.number() - if !r.Ok() { - return 0 - } - - n, err := strconv.ParseFloat(s, 32) - if err != nil { - r.addNonfatalError(&LexerError{ - Offset: r.start, - Reason: err.Error(), - Data: s, - }) - } - return float32(n) -} - -func (r *Lexer) Float32Str() float32 { - s, b := r.unsafeString(false) - if !r.Ok() { - return 0 - } - n, err := strconv.ParseFloat(s, 32) - if err != nil { - r.addNonfatalError(&LexerError{ - Offset: r.start, - Reason: err.Error(), - Data: string(b), - }) - } - return float32(n) -} - -func (r *Lexer) Float64() float64 { - s := r.number() - if !r.Ok() { - return 0 - } - - n, err := strconv.ParseFloat(s, 64) - if err != nil { - r.addNonfatalError(&LexerError{ - Offset: r.start, - Reason: err.Error(), - Data: s, - }) - } - return n -} - -func (r *Lexer) Float64Str() float64 { - s, b := r.unsafeString(false) - if !r.Ok() { - return 0 - } - n, err := strconv.ParseFloat(s, 64) - if err != nil { - r.addNonfatalError(&LexerError{ - Offset: r.start, - Reason: err.Error(), - Data: string(b), - }) - } - return n -} - -func (r *Lexer) Error() error { - return r.fatalError -} - -func (r *Lexer) AddError(e error) { - if r.fatalError == nil { - r.fatalError = e - } -} - -func (r *Lexer) AddNonFatalError(e error) { - r.addNonfatalError(&LexerError{ - Offset: r.start, - Data: string(r.Data[r.start:r.pos]), - Reason: e.Error(), - }) -} - -func (r *Lexer) addNonfatalError(err *LexerError) { - if r.UseMultipleErrors { - // We don't want to add errors with the same offset. - if len(r.multipleErrors) != 0 && r.multipleErrors[len(r.multipleErrors)-1].Offset == err.Offset { - return - } - r.multipleErrors = append(r.multipleErrors, err) - return - } - r.fatalError = err -} - -func (r *Lexer) GetNonFatalErrors() []*LexerError { - return r.multipleErrors -} - -// JsonNumber fetches and json.Number from 'encoding/json' package. -// Both int, float or string, contains them are valid values -func (r *Lexer) JsonNumber() json.Number { - if r.token.kind == TokenUndef && r.Ok() { - r.FetchToken() - } - if !r.Ok() { - r.errInvalidToken("json.Number") - return json.Number("") - } - - switch r.token.kind { - case TokenString: - return json.Number(r.String()) - case TokenNumber: - return json.Number(r.Raw()) - case TokenNull: - r.Null() - return json.Number("") - default: - r.errSyntax() - return json.Number("") - } -} - -// Interface fetches an interface{} analogous to the 'encoding/json' package. -func (r *Lexer) Interface() interface{} { - if r.token.kind == TokenUndef && r.Ok() { - r.FetchToken() - } - - if !r.Ok() { - return nil - } - switch r.token.kind { - case TokenString: - return r.String() - case TokenNumber: - return r.Float64() - case TokenBool: - return r.Bool() - case TokenNull: - r.Null() - return nil - } - - if r.token.delimValue == '{' { - r.consume() - - ret := map[string]interface{}{} - for !r.IsDelim('}') { - key := r.String() - r.WantColon() - ret[key] = r.Interface() - r.WantComma() - } - r.Delim('}') - - if r.Ok() { - return ret - } else { - return nil - } - } else if r.token.delimValue == '[' { - r.consume() - - ret := []interface{}{} - for !r.IsDelim(']') { - ret = append(ret, r.Interface()) - r.WantComma() - } - r.Delim(']') - - if r.Ok() { - return ret - } else { - return nil - } - } - r.errSyntax() - return nil -} - -// WantComma requires a comma to be present before fetching next token. -func (r *Lexer) WantComma() { - r.wantSep = ',' - r.firstElement = false -} - -// WantColon requires a colon to be present before fetching next token. -func (r *Lexer) WantColon() { - r.wantSep = ':' - r.firstElement = false -} - -// CurrentToken returns current token kind if there were no errors and TokenUndef otherwise -func (r *Lexer) CurrentToken() TokenKind { - if r.token.kind == TokenUndef && r.Ok() { - r.FetchToken() - } - - if !r.Ok() { - return TokenUndef - } - - return r.token.kind -} diff --git a/vendor/github.com/mailru/easyjson/jwriter/writer.go b/vendor/github.com/mailru/easyjson/jwriter/writer.go deleted file mode 100644 index 34b0ade46..000000000 --- a/vendor/github.com/mailru/easyjson/jwriter/writer.go +++ /dev/null @@ -1,417 +0,0 @@ -// Package jwriter contains a JSON writer. -package jwriter - -import ( - "io" - "strconv" - "unicode/utf8" - - "github.com/mailru/easyjson/buffer" -) - -// Flags describe various encoding options. The behavior may be actually implemented in the encoder, but -// Flags field in Writer is used to set and pass them around. -type Flags int - -const ( - NilMapAsEmpty Flags = 1 << iota // Encode nil map as '{}' rather than 'null'. - NilSliceAsEmpty // Encode nil slice as '[]' rather than 'null'. -) - -// Writer is a JSON writer. -type Writer struct { - Flags Flags - - Error error - Buffer buffer.Buffer - NoEscapeHTML bool -} - -// Size returns the size of the data that was written out. -func (w *Writer) Size() int { - return w.Buffer.Size() -} - -// DumpTo outputs the data to given io.Writer, resetting the buffer. -func (w *Writer) DumpTo(out io.Writer) (written int, err error) { - return w.Buffer.DumpTo(out) -} - -// BuildBytes returns writer data as a single byte slice. You can optionally provide one byte slice -// as argument that it will try to reuse. -func (w *Writer) BuildBytes(reuse ...[]byte) ([]byte, error) { - if w.Error != nil { - return nil, w.Error - } - - return w.Buffer.BuildBytes(reuse...), nil -} - -// ReadCloser returns an io.ReadCloser that can be used to read the data. -// ReadCloser also resets the buffer. -func (w *Writer) ReadCloser() (io.ReadCloser, error) { - if w.Error != nil { - return nil, w.Error - } - - return w.Buffer.ReadCloser(), nil -} - -// RawByte appends raw binary data to the buffer. -func (w *Writer) RawByte(c byte) { - w.Buffer.AppendByte(c) -} - -// RawByte appends raw binary data to the buffer. -func (w *Writer) RawString(s string) { - w.Buffer.AppendString(s) -} - -// RawBytesString appends string from bytes to the buffer. -func (w *Writer) RawBytesString(data []byte, err error) { - switch { - case w.Error != nil: - return - case err != nil: - w.Error = err - default: - w.String(string(data)) - } -} - -// Raw appends raw binary data to the buffer or sets the error if it is given. Useful for -// calling with results of MarshalJSON-like functions. -func (w *Writer) Raw(data []byte, err error) { - switch { - case w.Error != nil: - return - case err != nil: - w.Error = err - case len(data) > 0: - w.Buffer.AppendBytes(data) - default: - w.RawString("null") - } -} - -// RawText encloses raw binary data in quotes and appends in to the buffer. -// Useful for calling with results of MarshalText-like functions. -func (w *Writer) RawText(data []byte, err error) { - switch { - case w.Error != nil: - return - case err != nil: - w.Error = err - case len(data) > 0: - w.String(string(data)) - default: - w.RawString("null") - } -} - -// Base64Bytes appends data to the buffer after base64 encoding it -func (w *Writer) Base64Bytes(data []byte) { - if data == nil { - w.Buffer.AppendString("null") - return - } - w.Buffer.AppendByte('"') - w.base64(data) - w.Buffer.AppendByte('"') -} - -func (w *Writer) Uint8(n uint8) { - w.Buffer.EnsureSpace(3) - w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10) -} - -func (w *Writer) Uint16(n uint16) { - w.Buffer.EnsureSpace(5) - w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10) -} - -func (w *Writer) Uint32(n uint32) { - w.Buffer.EnsureSpace(10) - w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10) -} - -func (w *Writer) Uint(n uint) { - w.Buffer.EnsureSpace(20) - w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10) -} - -func (w *Writer) Uint64(n uint64) { - w.Buffer.EnsureSpace(20) - w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, n, 10) -} - -func (w *Writer) Int8(n int8) { - w.Buffer.EnsureSpace(4) - w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, int64(n), 10) -} - -func (w *Writer) Int16(n int16) { - w.Buffer.EnsureSpace(6) - w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, int64(n), 10) -} - -func (w *Writer) Int32(n int32) { - w.Buffer.EnsureSpace(11) - w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, int64(n), 10) -} - -func (w *Writer) Int(n int) { - w.Buffer.EnsureSpace(21) - w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, int64(n), 10) -} - -func (w *Writer) Int64(n int64) { - w.Buffer.EnsureSpace(21) - w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, n, 10) -} - -func (w *Writer) Uint8Str(n uint8) { - w.Buffer.EnsureSpace(3) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) Uint16Str(n uint16) { - w.Buffer.EnsureSpace(5) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) Uint32Str(n uint32) { - w.Buffer.EnsureSpace(10) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) UintStr(n uint) { - w.Buffer.EnsureSpace(20) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) Uint64Str(n uint64) { - w.Buffer.EnsureSpace(20) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, n, 10) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) UintptrStr(n uintptr) { - w.Buffer.EnsureSpace(20) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) Int8Str(n int8) { - w.Buffer.EnsureSpace(4) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, int64(n), 10) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) Int16Str(n int16) { - w.Buffer.EnsureSpace(6) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, int64(n), 10) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) Int32Str(n int32) { - w.Buffer.EnsureSpace(11) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, int64(n), 10) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) IntStr(n int) { - w.Buffer.EnsureSpace(21) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, int64(n), 10) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) Int64Str(n int64) { - w.Buffer.EnsureSpace(21) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, n, 10) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) Float32(n float32) { - w.Buffer.EnsureSpace(20) - w.Buffer.Buf = strconv.AppendFloat(w.Buffer.Buf, float64(n), 'g', -1, 32) -} - -func (w *Writer) Float32Str(n float32) { - w.Buffer.EnsureSpace(20) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendFloat(w.Buffer.Buf, float64(n), 'g', -1, 32) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) Float64(n float64) { - w.Buffer.EnsureSpace(20) - w.Buffer.Buf = strconv.AppendFloat(w.Buffer.Buf, n, 'g', -1, 64) -} - -func (w *Writer) Float64Str(n float64) { - w.Buffer.EnsureSpace(20) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendFloat(w.Buffer.Buf, float64(n), 'g', -1, 64) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) Bool(v bool) { - w.Buffer.EnsureSpace(5) - if v { - w.Buffer.Buf = append(w.Buffer.Buf, "true"...) - } else { - w.Buffer.Buf = append(w.Buffer.Buf, "false"...) - } -} - -const chars = "0123456789abcdef" - -func getTable(falseValues ...int) [128]bool { - table := [128]bool{} - - for i := 0; i < 128; i++ { - table[i] = true - } - - for _, v := range falseValues { - table[v] = false - } - - return table -} - -var ( - htmlEscapeTable = getTable(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, '"', '&', '<', '>', '\\') - htmlNoEscapeTable = getTable(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, '"', '\\') -) - -func (w *Writer) String(s string) { - w.Buffer.AppendByte('"') - - // Portions of the string that contain no escapes are appended as - // byte slices. - - p := 0 // last non-escape symbol - - escapeTable := &htmlEscapeTable - if w.NoEscapeHTML { - escapeTable = &htmlNoEscapeTable - } - - for i := 0; i < len(s); { - c := s[i] - - if c < utf8.RuneSelf { - if escapeTable[c] { - // single-width character, no escaping is required - i++ - continue - } - - w.Buffer.AppendString(s[p:i]) - switch c { - case '\t': - w.Buffer.AppendString(`\t`) - case '\r': - w.Buffer.AppendString(`\r`) - case '\n': - w.Buffer.AppendString(`\n`) - case '\\': - w.Buffer.AppendString(`\\`) - case '"': - w.Buffer.AppendString(`\"`) - default: - w.Buffer.AppendString(`\u00`) - w.Buffer.AppendByte(chars[c>>4]) - w.Buffer.AppendByte(chars[c&0xf]) - } - - i++ - p = i - continue - } - - // broken utf - runeValue, runeWidth := utf8.DecodeRuneInString(s[i:]) - if runeValue == utf8.RuneError && runeWidth == 1 { - w.Buffer.AppendString(s[p:i]) - w.Buffer.AppendString(`\ufffd`) - i++ - p = i - continue - } - - // jsonp stuff - tab separator and line separator - if runeValue == '\u2028' || runeValue == '\u2029' { - w.Buffer.AppendString(s[p:i]) - w.Buffer.AppendString(`\u202`) - w.Buffer.AppendByte(chars[runeValue&0xf]) - i += runeWidth - p = i - continue - } - i += runeWidth - } - w.Buffer.AppendString(s[p:]) - w.Buffer.AppendByte('"') -} - -const encode = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" -const padChar = '=' - -func (w *Writer) base64(in []byte) { - - if len(in) == 0 { - return - } - - w.Buffer.EnsureSpace(((len(in)-1)/3 + 1) * 4) - - si := 0 - n := (len(in) / 3) * 3 - - for si < n { - // Convert 3x 8bit source bytes into 4 bytes - val := uint(in[si+0])<<16 | uint(in[si+1])<<8 | uint(in[si+2]) - - w.Buffer.Buf = append(w.Buffer.Buf, encode[val>>18&0x3F], encode[val>>12&0x3F], encode[val>>6&0x3F], encode[val&0x3F]) - - si += 3 - } - - remain := len(in) - si - if remain == 0 { - return - } - - // Add the remaining small block - val := uint(in[si+0]) << 16 - if remain == 2 { - val |= uint(in[si+1]) << 8 - } - - w.Buffer.Buf = append(w.Buffer.Buf, encode[val>>18&0x3F], encode[val>>12&0x3F]) - - switch remain { - case 2: - w.Buffer.Buf = append(w.Buffer.Buf, encode[val>>6&0x3F], byte(padChar)) - case 1: - w.Buffer.Buf = append(w.Buffer.Buf, byte(padChar), byte(padChar)) - } -} diff --git a/vendor/github.com/mailru/easyjson/raw.go b/vendor/github.com/mailru/easyjson/raw.go deleted file mode 100644 index ee7367ae5..000000000 --- a/vendor/github.com/mailru/easyjson/raw.go +++ /dev/null @@ -1,46 +0,0 @@ -package easyjson - -import ( - "github.com/mailru/easyjson/jlexer" - "github.com/mailru/easyjson/jwriter" -) - -// RawMessage is a raw piece of JSON (number, string, bool, object, array or -// null) that is extracted without parsing and output as is during marshaling. -type RawMessage []byte - -// MarshalEasyJSON does JSON marshaling using easyjson interface. -func (v *RawMessage) MarshalEasyJSON(w *jwriter.Writer) { - if len(*v) == 0 { - w.RawString("null") - } else { - w.Raw(*v, nil) - } -} - -// UnmarshalEasyJSON does JSON unmarshaling using easyjson interface. -func (v *RawMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - *v = RawMessage(l.Raw()) -} - -// UnmarshalJSON implements encoding/json.Unmarshaler interface. -func (v *RawMessage) UnmarshalJSON(data []byte) error { - *v = make([]byte, len(data)) - copy(*v, data) - return nil -} - -var nullBytes = []byte("null") - -// MarshalJSON implements encoding/json.Marshaler interface. -func (v RawMessage) MarshalJSON() ([]byte, error) { - if len(v) == 0 { - return nullBytes, nil - } - return v, nil -} - -// IsDefined is required for integration with omitempty easyjson logic. -func (v *RawMessage) IsDefined() bool { - return len(*v) > 0 -} diff --git a/vendor/github.com/mailru/easyjson/unknown_fields.go b/vendor/github.com/mailru/easyjson/unknown_fields.go deleted file mode 100644 index 55538eac9..000000000 --- a/vendor/github.com/mailru/easyjson/unknown_fields.go +++ /dev/null @@ -1,32 +0,0 @@ -package easyjson - -import ( - jlexer "github.com/mailru/easyjson/jlexer" - "github.com/mailru/easyjson/jwriter" -) - -// UnknownFieldsProxy implemets UnknownsUnmarshaler and UnknownsMarshaler -// use it as embedded field in your structure to parse and then serialize unknown struct fields -type UnknownFieldsProxy struct { - unknownFields map[string][]byte -} - -func (s *UnknownFieldsProxy) UnmarshalUnknown(in *jlexer.Lexer, key string) { - if s.unknownFields == nil { - s.unknownFields = make(map[string][]byte, 1) - } - s.unknownFields[key] = in.Raw() -} - -func (s UnknownFieldsProxy) MarshalUnknowns(out *jwriter.Writer, first bool) { - for key, val := range s.unknownFields { - if first { - first = false - } else { - out.RawByte(',') - } - out.String(string(key)) - out.RawByte(':') - out.Raw(val, nil) - } -} diff --git a/vendor/github.com/operator-framework/api/LICENSE b/vendor/github.com/operator-framework/api/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/vendor/github.com/operator-framework/api/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/vendor/github.com/operator-framework/api/pkg/lib/release/release.go b/vendor/github.com/operator-framework/api/pkg/lib/release/release.go new file mode 100644 index 000000000..e91adf4c3 --- /dev/null +++ b/vendor/github.com/operator-framework/api/pkg/lib/release/release.go @@ -0,0 +1,73 @@ +package release + +import ( + "encoding/json" + "slices" + "strings" + + semver "github.com/blang/semver/v4" +) + +// +k8s:openapi-gen=true +// OperatorRelease is a wrapper around a slice of semver.PRVersion which supports correct +// marshaling to YAML and JSON. +// +kubebuilder:validation:Type=string +// +kubebuilder:validation:MaxLength=20 +// +kubebuilder:validation:XValidation:rule="self.matches('^[0-9A-Za-z-]+(\\\\.[0-9A-Za-z-]+)*$')",message="release version must be composed of dot-separated identifiers containing only alphanumerics and hyphens" +// +kubebuilder:validation:XValidation:rule="!self.split('.').exists(x, x.matches('^0[0-9]+$'))",message="numeric identifiers in release version must not have leading zeros" +type OperatorRelease struct { + Release []semver.PRVersion `json:"-"` +} + +// DeepCopyInto creates a deep-copy of the Version value. +func (v *OperatorRelease) DeepCopyInto(out *OperatorRelease) { + out.Release = slices.Clone(v.Release) +} + +// MarshalJSON implements the encoding/json.Marshaler interface. +func (v OperatorRelease) MarshalJSON() ([]byte, error) { + segments := []string{} + for _, segment := range v.Release { + segments = append(segments, segment.String()) + } + return json.Marshal(strings.Join(segments, ".")) +} + +// UnmarshalJSON implements the encoding/json.Unmarshaler interface. +func (v *OperatorRelease) UnmarshalJSON(data []byte) (err error) { + var versionString string + + if err = json.Unmarshal(data, &versionString); err != nil { + return + } + + segments := strings.Split(versionString, ".") + for _, segment := range segments { + release, err := semver.NewPRVersion(segment) + if err != nil { + return err + } + v.Release = append(v.Release, release) + } + + return nil +} + +// OpenAPISchemaType is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +// +// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators +func (_ OperatorRelease) OpenAPISchemaType() []string { return []string{"string"} } + +// OpenAPISchemaFormat is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +// "semver" is not a standard openapi format but tooling may use the value regardless +func (_ OperatorRelease) OpenAPISchemaFormat() string { return "semver" } + +func (r OperatorRelease) String() string { + segments := []string{} + for _, segment := range r.Release { + segments = append(segments, segment.String()) + } + return strings.Join(segments, ".") +} diff --git a/vendor/github.com/operator-framework/api/pkg/lib/version/doc.go b/vendor/github.com/operator-framework/api/pkg/lib/version/doc.go new file mode 100644 index 000000000..7318ee6d1 --- /dev/null +++ b/vendor/github.com/operator-framework/api/pkg/lib/version/doc.go @@ -0,0 +1,5 @@ +// +k8s:openapi-gen=true +// +k8s:openapi-model-package=com.github.operator-framework.api.pkg.operators.lib.version + +// Package version contains the OperatorVersion type. +package version diff --git a/vendor/github.com/operator-framework/api/pkg/lib/version/version.go b/vendor/github.com/operator-framework/api/pkg/lib/version/version.go new file mode 100644 index 000000000..a0ffb9fcb --- /dev/null +++ b/vendor/github.com/operator-framework/api/pkg/lib/version/version.go @@ -0,0 +1,67 @@ +package version + +import ( + "encoding/json" + + semver "github.com/blang/semver/v4" +) + +// +k8s:openapi-gen=true +// OperatorVersion is a wrapper around semver.Version which supports correct +// marshaling to YAML and JSON. +// +kubebuilder:validation:Type=string +type OperatorVersion struct { + semver.Version `json:"-"` +} + +// DeepCopyInto creates a deep-copy of the Version value. +func (v *OperatorVersion) DeepCopyInto(out *OperatorVersion) { + out.Major = v.Major + out.Minor = v.Minor + out.Patch = v.Patch + + if v.Pre != nil { + pre := make([]semver.PRVersion, len(v.Pre)) + copy(pre, v.Pre) + out.Pre = pre + } + + if v.Build != nil { + build := make([]string, len(v.Build)) + copy(build, v.Build) + out.Build = build + } +} + +// MarshalJSON implements the encoding/json.Marshaler interface. +func (v OperatorVersion) MarshalJSON() ([]byte, error) { + return json.Marshal(v.String()) +} + +// UnmarshalJSON implements the encoding/json.Unmarshaler interface. +func (v *OperatorVersion) UnmarshalJSON(data []byte) (err error) { + var versionString string + + if err = json.Unmarshal(data, &versionString); err != nil { + return + } + + version := semver.Version{} + version, err = semver.ParseTolerant(versionString) + if err != nil { + return err + } + v.Version = version + return +} + +// OpenAPISchemaType is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +// +// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators +func (_ OperatorVersion) OpenAPISchemaType() []string { return []string{"string"} } + +// OpenAPISchemaFormat is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +// "semver" is not a standard openapi format but tooling may use the value regardless +func (_ OperatorVersion) OpenAPISchemaFormat() string { return "semver" } diff --git a/vendor/github.com/operator-framework/api/pkg/lib/version/zz_generated.model_name.go b/vendor/github.com/operator-framework/api/pkg/lib/version/zz_generated.model_name.go new file mode 100644 index 000000000..1fd31307b --- /dev/null +++ b/vendor/github.com/operator-framework/api/pkg/lib/version/zz_generated.model_name.go @@ -0,0 +1,26 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* + +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. +*/ + +// Code generated by openapi-gen-v0.0. DO NOT EDIT. + +package version + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in OperatorVersion) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.lib.version.OperatorVersion" +} diff --git a/vendor/github.com/operator-framework/api/pkg/operators/doc.go b/vendor/github.com/operator-framework/api/pkg/operators/doc.go new file mode 100644 index 000000000..7eba79448 --- /dev/null +++ b/vendor/github.com/operator-framework/api/pkg/operators/doc.go @@ -0,0 +1,4 @@ +// +kubebuilder:skip + +// Package operators contains all resource types of the operators.coreos.com API group. +package operators diff --git a/vendor/github.com/operator-framework/api/pkg/operators/register.go b/vendor/github.com/operator-framework/api/pkg/operators/register.go new file mode 100644 index 000000000..e3c31d51a --- /dev/null +++ b/vendor/github.com/operator-framework/api/pkg/operators/register.go @@ -0,0 +1,31 @@ +package operators + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +const ( + // GroupName is the group name used in this package. + GroupName = "operators.coreos.com" + // GroupVersion is the group version used in this package. + GroupVersion = runtime.APIVersionInternal + + // LEGACY: Exported kind names, remove after major version bump + + // ClusterServiceVersionKind is the kind name for ClusterServiceVersion resources. + ClusterServiceVersionKind = "ClusterServiceVersion" + // CatalogSourceKind is the kind name for CatalogSource resources. + CatalogSourceKind = "CatalogSource" + // InstallPlanKind is the kind name for InstallPlan resources. + InstallPlanKind = "InstallPlan" + // SubscriptionKind is the kind name for Subscription resources. + SubscriptionKind = "Subscription" + // OperatorKind is the kind name for Operator resources. + OperatorKind = "Operator" + // OperatorGroupKind is the kind name for OperatorGroup resources. + OperatorGroupKind = "OperatorGroup" +) + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal} diff --git a/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/catalogsource_types.go b/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/catalogsource_types.go new file mode 100644 index 000000000..8386b2032 --- /dev/null +++ b/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/catalogsource_types.go @@ -0,0 +1,364 @@ +package v1alpha1 + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +const ( + CatalogSourceCRDAPIVersion = GroupName + "/" + GroupVersion + CatalogSourceKind = "CatalogSource" + DefaultRegistryPollDuration = 15 * time.Minute +) + +// SourceType indicates the type of backing store for a CatalogSource +type SourceType string + +const ( + // SourceTypeInternal (deprecated) specifies a CatalogSource of type SourceTypeConfigmap + SourceTypeInternal SourceType = "internal" + + // SourceTypeConfigmap specifies a CatalogSource that generates a configmap-server registry + SourceTypeConfigmap SourceType = "configmap" + + // SourceTypeGrpc specifies a CatalogSource that can use an operator registry image to generate a + // registry-server or connect to a pre-existing registry at an address. + SourceTypeGrpc SourceType = "grpc" +) + +const ( + // CatalogSourceSpecInvalidError denotes when fields on the spec of the CatalogSource are not valid. + CatalogSourceSpecInvalidError ConditionReason = "SpecInvalidError" + // CatalogSourceConfigMapError denotes when there is an issue extracting manifests from the specified ConfigMap. + CatalogSourceConfigMapError ConditionReason = "ConfigMapError" + // CatalogSourceRegistryServerError denotes when there is an issue querying the specified registry server. + CatalogSourceRegistryServerError ConditionReason = "RegistryServerError" + // CatalogSourceIntervalInvalidError denotes if the registry polling interval is invalid. + CatalogSourceIntervalInvalidError ConditionReason = "InvalidIntervalError" +) + +type CatalogSourceSpec struct { + // SourceType is the type of source + SourceType SourceType `json:"sourceType"` + + // Priority field assigns a weight to the catalog source to prioritize them so that it can be consumed by the dependency resolver. + // Usage: + // Higher weight indicates that this catalog source is preferred over lower weighted catalog sources during dependency resolution. + // The range of the priority value can go from positive to negative in the range of int32. + // The default value to a catalog source with unassigned priority would be 0. + // The catalog source with the same priority values will be ranked lexicographically based on its name. + // +optional + Priority int `json:"priority,omitempty"` + + // ConfigMap is the name of the ConfigMap to be used to back a configmap-server registry. + // Only used when SourceType = SourceTypeConfigmap or SourceTypeInternal. + // +optional + ConfigMap string `json:"configMap,omitempty"` + + // Address is a host that OLM can use to connect to a pre-existing registry. + // Format: : + // Only used when SourceType = SourceTypeGrpc. + // Ignored when the Image field is set. + // +optional + Address string `json:"address,omitempty"` + + // Image is an operator-registry container image to instantiate a registry-server with. + // Only used when SourceType = SourceTypeGrpc. + // If present, the address field is ignored. + // +optional + Image string `json:"image,omitempty"` + + // GrpcPodConfig exposes different overrides for the pod spec of the CatalogSource Pod. + // Only used when SourceType = SourceTypeGrpc and Image is set. + // +optional + GrpcPodConfig *GrpcPodConfig `json:"grpcPodConfig,omitempty"` + + // UpdateStrategy defines how updated catalog source images can be discovered + // Consists of an interval that defines polling duration and an embedded strategy type + // +optional + UpdateStrategy *UpdateStrategy `json:"updateStrategy,omitempty"` + + // Secrets represent set of secrets that can be used to access the contents of the catalog. + // It is best to keep this list small, since each will need to be tried for every catalog entry. + // +optional + Secrets []string `json:"secrets,omitempty"` + + // Metadata + DisplayName string `json:"displayName,omitempty"` + Description string `json:"description,omitempty"` + Publisher string `json:"publisher,omitempty"` + Icon Icon `json:"icon,omitempty"` +} + +type SecurityConfig string + +const ( + Legacy SecurityConfig = "legacy" + Restricted SecurityConfig = "restricted" +) + +// GrpcPodConfig contains configuration specified for a catalog source +type GrpcPodConfig struct { + // NodeSelector is a selector which must be true for the pod to fit on a node. + // Selector which must match a node's labels for the pod to be scheduled on that node. + // +optional + NodeSelector map[string]string `json:"nodeSelector,omitempty"` + + // Tolerations are the catalog source's pod's tolerations. + // +optional + Tolerations []corev1.Toleration `json:"tolerations,omitempty"` + + // Affinity is the catalog source's pod's affinity. + // +optional + Affinity *corev1.Affinity `json:"affinity,omitempty"` + + // If specified, indicates the pod's priority. + // If not specified, the pod priority will be default or zero if there is no + // default. + // +optional + PriorityClassName *string `json:"priorityClassName,omitempty"` + + // SecurityContextConfig can be one of `legacy` or `restricted`. The CatalogSource's pod is either injected with the + // right pod.spec.securityContext and pod.spec.container[*].securityContext values to allow the pod to run in Pod + // Security Admission (PSA) `restricted` mode, or doesn't set these values at all, in which case the pod can only be + // run in PSA `baseline` or `privileged` namespaces. If the SecurityContextConfig is unspecified, the mode will be + // determined by the namespace's PSA configuration. If the namespace is enforcing `restricted` mode, then the pod + // will be configured as if `restricted` was specified. Otherwise, it will be configured as if `legacy` was + // specified. Specifying a value other than `legacy` or `restricted` result in a validation error. When using older + // catalog images, which can not run in `restricted` mode, the SecurityContextConfig should be set to `legacy`. + // + // More information about PSA can be found here: https://kubernetes.io/docs/concepts/security/pod-security-admission/ + // +optional + // +kubebuilder:validation:Enum=legacy;restricted + SecurityContextConfig SecurityConfig `json:"securityContextConfig,omitempty"` + + // MemoryTarget configures the $GOMEMLIMIT value for the gRPC catalog Pod. This is a soft memory limit for the server, + // which the runtime will attempt to meet but makes no guarantees that it will do so. If this value is set, the Pod + // will have the following modifications made to the container running the server: + // - the $GOMEMLIMIT environment variable will be set to this value in bytes + // - the memory request will be set to this value + // + // This field should be set if it's desired to reduce the footprint of a catalog server as much as possible, or if + // a catalog being served is very large and needs more than the default allocation. If your index image has a file- + // system cache, determine a good approximation for this value by doubling the size of the package cache at + // /tmp/cache/cache/packages.json in the index image. + // + // This field is best-effort; if unset, no default will be used and no Pod memory limit or $GOMEMLIMIT value will be set. + // +optional + MemoryTarget *resource.Quantity `json:"memoryTarget,omitempty"` + + // ExtractContent configures the gRPC catalog Pod to extract catalog metadata from the provided index image and + // use a well-known version of the `opm` server to expose it. The catalog index image that this CatalogSource is + // configured to use *must* be using the file-based catalogs in order to utilize this feature. + // +optional + ExtractContent *ExtractContentConfig `json:"extractContent,omitempty"` +} + +// ExtractContentConfig configures context extraction from a file-based catalog index image. +type ExtractContentConfig struct { + // CacheDir is the (optional) directory storing the pre-calculated API cache. + CacheDir string `json:"cacheDir,omitempty"` + // CatalogDir is the directory storing the file-based catalog contents. + CatalogDir string `json:"catalogDir"` +} + +// UpdateStrategy holds all the different types of catalog source update strategies +// Currently only registry polling strategy is implemented +type UpdateStrategy struct { + *RegistryPoll `json:"registryPoll,omitempty"` +} + +type RegistryPoll struct { + // Interval is used to determine the time interval between checks of the latest catalog source version. + // The catalog operator polls to see if a new version of the catalog source is available. + // If available, the latest image is pulled and gRPC traffic is directed to the latest catalog source. + RawInterval string `json:"interval,omitempty"` + Interval *metav1.Duration `json:"-"` + ParsingError string `json:"-"` +} + +// UnmarshalJSON implements the encoding/json.Unmarshaler interface. +func (u *UpdateStrategy) UnmarshalJSON(data []byte) (err error) { + type alias struct { + *RegistryPoll `json:"registryPoll,omitempty"` + } + us := alias{} + if err = json.Unmarshal(data, &us); err != nil { + return err + } + registryPoll := &RegistryPoll{ + RawInterval: us.RegistryPoll.RawInterval, + } + duration, err := time.ParseDuration(registryPoll.RawInterval) + if err != nil { + registryPoll.ParsingError = fmt.Sprintf("error parsing spec.updateStrategy.registryPoll.interval. Using the default value of %s instead. Error: %s", DefaultRegistryPollDuration, err) + registryPoll.Interval = &metav1.Duration{Duration: DefaultRegistryPollDuration} + } else { + registryPoll.Interval = &metav1.Duration{Duration: duration} + } + u.RegistryPoll = registryPoll + return nil +} + +type RegistryServiceStatus struct { + Protocol string `json:"protocol,omitempty"` + ServiceName string `json:"serviceName,omitempty"` + ServiceNamespace string `json:"serviceNamespace,omitempty"` + Port string `json:"port,omitempty"` + CreatedAt metav1.Time `json:"createdAt,omitempty"` +} + +func (s *RegistryServiceStatus) Address() string { + return fmt.Sprintf("%s.%s.svc:%s", s.ServiceName, s.ServiceNamespace, s.Port) +} + +type GRPCConnectionState struct { + Address string `json:"address,omitempty"` + LastObservedState string `json:"lastObservedState"` + LastConnectTime metav1.Time `json:"lastConnect,omitempty"` +} + +type CatalogSourceStatus struct { + // A human readable message indicating details about why the CatalogSource is in this condition. + // +optional + Message string `json:"message,omitempty"` + // Reason is the reason the CatalogSource was transitioned to its current state. + // +optional + Reason ConditionReason `json:"reason,omitempty"` + + // The last time the CatalogSource image registry has been polled to ensure the image is up-to-date + LatestImageRegistryPoll *metav1.Time `json:"latestImageRegistryPoll,omitempty"` + + // ConfigMapReference (deprecated) is the reference to the ConfigMap containing the catalog source's configuration, when the catalog source is a ConfigMap + ConfigMapResource *ConfigMapResourceReference `json:"configMapReference,omitempty"` + // RegistryService represents the current state of the GRPC service used to serve the catalog + RegistryServiceStatus *RegistryServiceStatus `json:"registryService,omitempty"` + // ConnectionState represents the current state of the CatalogSource's connection to the registry + GRPCConnectionState *GRPCConnectionState `json:"connectionState,omitempty"` + + // Represents the state of a CatalogSource. Note that Message and Reason represent the original + // status information, which may be migrated to be conditions based in the future. Any new features + // introduced will use conditions. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` +} + +type ConfigMapResourceReference struct { + Name string `json:"name"` + Namespace string `json:"namespace"` + UID types.UID `json:"uid,omitempty"` + ResourceVersion string `json:"resourceVersion,omitempty"` + LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"` +} + +func (r *ConfigMapResourceReference) IsAMatch(object *metav1.ObjectMeta) bool { + return r.UID == object.GetUID() && r.ResourceVersion == object.GetResourceVersion() +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +genclient +// +kubebuilder:resource:shortName=catsrc,categories=olm +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Display",type=string,JSONPath=`.spec.displayName`,description="The pretty name of the catalog" +// +kubebuilder:printcolumn:name="Type",type=string,JSONPath=`.spec.sourceType`,description="The type of the catalog" +// +kubebuilder:printcolumn:name="Publisher",type=string,JSONPath=`.spec.publisher`,description="The publisher of the catalog" +// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` + +// CatalogSource is a repository of CSVs, CRDs, and operator packages. +type CatalogSource struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + + Spec CatalogSourceSpec `json:"spec"` + // +optional + Status CatalogSourceStatus `json:"status"` +} + +func (c *CatalogSource) Address() string { + if c.Spec.Address != "" { + return c.Spec.Address + } + return c.Status.RegistryServiceStatus.Address() +} + +func (c *CatalogSource) SetError(reason ConditionReason, err error) { + c.Status.Reason = reason + c.Status.Message = "" + if err != nil { + c.Status.Message = err.Error() + } +} + +func (c *CatalogSource) SetLastUpdateTime() { + now := metav1.Now() + c.Status.LatestImageRegistryPoll = &now +} + +// Check if it is time to update based on polling setting +func (c *CatalogSource) Update() bool { + if !c.Poll() { + return false + } + interval := c.Spec.UpdateStrategy.Interval.Duration + latest := c.Status.LatestImageRegistryPoll + if latest == nil { + logrus.WithField("CatalogSource", c.Name).Debugf("latest poll %v", latest) + } else { + logrus.WithField("CatalogSource", c.Name).Debugf("latest poll %v", *c.Status.LatestImageRegistryPoll) + } + + if c.Status.LatestImageRegistryPoll.IsZero() { + logrus.WithField("CatalogSource", c.Name).Debugf("creation timestamp plus interval before now %t", c.CreationTimestamp.Add(interval).Before(time.Now())) + if c.CreationTimestamp.Add(interval).Before(time.Now()) { + return true + } + } else { + logrus.WithField("CatalogSource", c.Name).Debugf("latest poll plus interval before now %t", c.Status.LatestImageRegistryPoll.Add(interval).Before(time.Now())) + if c.Status.LatestImageRegistryPoll.Add(interval).Before(time.Now()) { + return true + } + } + + return false +} + +// Poll determines whether the polling feature is enabled on the particular catalog source +func (c *CatalogSource) Poll() bool { + if c.Spec.UpdateStrategy == nil { + return false + } + // if polling interval is zero polling will not be done + if c.Spec.UpdateStrategy.RegistryPoll == nil { + return false + } + // if catalog source is not backed by an image polling will not be done + if c.Spec.Image == "" { + return false + } + // if image is not type gRPC polling will not be done + if c.Spec.SourceType != SourceTypeGrpc { + return false + } + return true +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// CatalogSourceList is a repository of CSVs, CRDs, and operator packages. +type CatalogSourceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []CatalogSource `json:"items"` +} diff --git a/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/clusterserviceversion.go b/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/clusterserviceversion.go new file mode 100644 index 000000000..a4c8d1746 --- /dev/null +++ b/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/clusterserviceversion.go @@ -0,0 +1,215 @@ +package v1alpha1 + +import ( + "fmt" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/record" +) + +const ( + CopiedLabelKey = "olm.copiedFrom" + + // ConditionsLengthLimit is the maximum length of Status.Conditions of a + // given ClusterServiceVersion object. The oldest condition(s) are removed + // from the list as it grows over time to keep it at limit. + ConditionsLengthLimit = 20 +) + +// obsoleteReasons are the set of reasons that mean a CSV should no longer be processed as active +var obsoleteReasons = map[ConditionReason]struct{}{ + CSVReasonReplaced: {}, + CSVReasonBeingReplaced: {}, +} + +// uncopiableReasons are the set of reasons that should prevent a CSV from being copied to target namespaces +var uncopiableReasons = map[ConditionReason]struct{}{ + CSVReasonCopied: {}, + CSVReasonInvalidInstallModes: {}, + CSVReasonNoTargetNamespaces: {}, + CSVReasonUnsupportedOperatorGroup: {}, + CSVReasonNoOperatorGroup: {}, + CSVReasonTooManyOperatorGroups: {}, + CSVReasonInterOperatorGroupOwnerConflict: {}, + CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs: {}, +} + +// safeToAnnotateOperatorGroupReasons are the set of reasons that it's safe to attempt to update the operatorgroup +// annotations +var safeToAnnotateOperatorGroupReasons = map[ConditionReason]struct{}{ + CSVReasonOwnerConflict: {}, + CSVReasonInstallSuccessful: {}, + CSVReasonInvalidInstallModes: {}, + CSVReasonNoTargetNamespaces: {}, + CSVReasonUnsupportedOperatorGroup: {}, + CSVReasonNoOperatorGroup: {}, + CSVReasonTooManyOperatorGroups: {}, + CSVReasonInterOperatorGroupOwnerConflict: {}, + CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs: {}, +} + +// SetPhaseWithEventIfChanged emits a Kubernetes event with details of a phase change and sets the current phase if phase, reason, or message would changed +func (c *ClusterServiceVersion) SetPhaseWithEventIfChanged(phase ClusterServiceVersionPhase, reason ConditionReason, message string, now *metav1.Time, recorder record.EventRecorder) { + if c.Status.Phase == phase && c.Status.Reason == reason && c.Status.Message == message { + return + } + + c.SetPhaseWithEvent(phase, reason, message, now, recorder) +} + +// SetPhaseWithEvent generates a Kubernetes event with details about the phase change and sets the current phase +func (c *ClusterServiceVersion) SetPhaseWithEvent(phase ClusterServiceVersionPhase, reason ConditionReason, message string, now *metav1.Time, recorder record.EventRecorder) { + var eventtype string + if phase == CSVPhaseFailed { + eventtype = v1.EventTypeWarning + } else { + eventtype = v1.EventTypeNormal + } + go recorder.Event(c, eventtype, string(reason), message) + c.SetPhase(phase, reason, message, now) +} + +// SetPhase sets the current phase and adds a condition if necessary +func (c *ClusterServiceVersion) SetPhase(phase ClusterServiceVersionPhase, reason ConditionReason, message string, now *metav1.Time) { + newCondition := func() ClusterServiceVersionCondition { + return ClusterServiceVersionCondition{ + Phase: c.Status.Phase, + LastTransitionTime: c.Status.LastTransitionTime, + LastUpdateTime: c.Status.LastUpdateTime, + Message: message, + Reason: reason, + } + } + + defer c.TrimConditionsIfLimitExceeded() + + c.Status.LastUpdateTime = now + if c.Status.Phase != phase { + c.Status.Phase = phase + c.Status.LastTransitionTime = now + } + c.Status.Message = message + c.Status.Reason = reason + if len(c.Status.Conditions) == 0 { + c.Status.Conditions = append(c.Status.Conditions, newCondition()) + return + } + + previousCondition := c.Status.Conditions[len(c.Status.Conditions)-1] + if previousCondition.Phase != c.Status.Phase || previousCondition.Reason != c.Status.Reason { + c.Status.Conditions = append(c.Status.Conditions, newCondition()) + } +} + +// SetRequirementStatus adds the status of all requirements to the CSV status +func (c *ClusterServiceVersion) SetRequirementStatus(statuses []RequirementStatus) { + c.Status.RequirementStatus = statuses +} + +// IsObsolete returns if this CSV is being replaced or is marked for deletion +func (c *ClusterServiceVersion) IsObsolete() bool { + for _, condition := range c.Status.Conditions { + _, ok := obsoleteReasons[condition.Reason] + if ok { + return true + } + } + return false +} + +// IsCopied returns true if the CSV has been copied and false otherwise. +func (c *ClusterServiceVersion) IsCopied() bool { + return c.Status.Reason == CSVReasonCopied || IsCopied(c) +} + +func IsCopied(o metav1.Object) bool { + annotations := o.GetAnnotations() + if annotations != nil { + operatorNamespace, ok := annotations[OperatorGroupNamespaceAnnotationKey] + if ok && o.GetNamespace() != operatorNamespace { + return true + } + } + + if labels := o.GetLabels(); labels != nil { + if _, ok := labels[CopiedLabelKey]; ok { + return true + } + } + return false +} + +func (c *ClusterServiceVersion) IsUncopiable() bool { + if c.Status.Phase == CSVPhaseNone { + return true + } + _, ok := uncopiableReasons[c.Status.Reason] + return ok +} + +func (c *ClusterServiceVersion) IsSafeToUpdateOperatorGroupAnnotations() bool { + _, ok := safeToAnnotateOperatorGroupReasons[c.Status.Reason] + return ok +} + +// NewInstallModeSet returns an InstallModeSet instantiated from the given list of InstallModes. +// If the given list is not a set, an error is returned. +func NewInstallModeSet(modes []InstallMode) (InstallModeSet, error) { + set := InstallModeSet{} + for _, mode := range modes { + if _, exists := set[mode.Type]; exists { + return nil, fmt.Errorf("InstallMode list contains duplicates, cannot make set: %v", modes) + } + set[mode.Type] = mode.Supported + } + + return set, nil +} + +// Supports returns an error if the InstallModeSet does not support configuration for +// the given operatorNamespace and list of target namespaces. +func (set InstallModeSet) Supports(operatorNamespace string, namespaces []string) error { + numNamespaces := len(namespaces) + switch { + case numNamespaces == 0: + return fmt.Errorf("operatorgroup has invalid selected namespaces, cannot configure to watch zero namespaces") + case numNamespaces == 1: + switch namespaces[0] { + case operatorNamespace: + if !set[InstallModeTypeOwnNamespace] { + return fmt.Errorf("%s InstallModeType not supported, cannot configure to watch own namespace", InstallModeTypeOwnNamespace) + } + case v1.NamespaceAll: + if !set[InstallModeTypeAllNamespaces] { + return fmt.Errorf("%s InstallModeType not supported, cannot configure to watch all namespaces", InstallModeTypeAllNamespaces) + } + default: + if !set[InstallModeTypeSingleNamespace] { + return fmt.Errorf("%s InstallModeType not supported, cannot configure to watch one namespace", InstallModeTypeSingleNamespace) + } + } + case numNamespaces > 1 && !set[InstallModeTypeMultiNamespace]: + return fmt.Errorf("%s InstallModeType not supported, cannot configure to watch %d namespaces", InstallModeTypeMultiNamespace, numNamespaces) + case numNamespaces > 1: + for _, namespace := range namespaces { + if namespace == operatorNamespace && !set[InstallModeTypeOwnNamespace] { + return fmt.Errorf("%s InstallModeType not supported, cannot configure to watch own namespace", InstallModeTypeOwnNamespace) + } + if namespace == v1.NamespaceAll { + return fmt.Errorf("operatorgroup has invalid selected namespaces, NamespaceAll found when |selected namespaces| > 1") + } + } + } + + return nil +} + +func (c *ClusterServiceVersion) TrimConditionsIfLimitExceeded() { + if len(c.Status.Conditions) <= ConditionsLengthLimit { + return + } + + firstIndex := len(c.Status.Conditions) - ConditionsLengthLimit + c.Status.Conditions = c.Status.Conditions[firstIndex:len(c.Status.Conditions)] +} diff --git a/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/clusterserviceversion_types.go b/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/clusterserviceversion_types.go new file mode 100644 index 000000000..1efb4323c --- /dev/null +++ b/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/clusterserviceversion_types.go @@ -0,0 +1,756 @@ +package v1alpha1 + +import ( + "encoding/json" + "fmt" + "sort" + "strings" + + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + appsv1 "k8s.io/api/apps/v1" + rbac "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/intstr" + + "github.com/operator-framework/api/pkg/lib/release" + "github.com/operator-framework/api/pkg/lib/version" +) + +const ( + ClusterServiceVersionAPIVersion = GroupName + "/" + GroupVersion + ClusterServiceVersionKind = "ClusterServiceVersion" + OperatorGroupNamespaceAnnotationKey = "olm.operatorNamespace" + InstallStrategyNameDeployment = "deployment" + SkipRangeAnnotationKey = "olm.skipRange" +) + +// InstallModeType is a supported type of install mode for CSV installation +type InstallModeType string + +const ( + // InstallModeTypeOwnNamespace indicates that the operator can be a member of an `OperatorGroup` that selects its own namespace. + InstallModeTypeOwnNamespace InstallModeType = "OwnNamespace" + // InstallModeTypeSingleNamespace indicates that the operator can be a member of an `OperatorGroup` that selects one namespace. + InstallModeTypeSingleNamespace InstallModeType = "SingleNamespace" + // InstallModeTypeMultiNamespace indicates that the operator can be a member of an `OperatorGroup` that selects more than one namespace. + InstallModeTypeMultiNamespace InstallModeType = "MultiNamespace" + // InstallModeTypeAllNamespaces indicates that the operator can be a member of an `OperatorGroup` that selects all namespaces (target namespace set is the empty string ""). + InstallModeTypeAllNamespaces InstallModeType = "AllNamespaces" +) + +// InstallMode associates an InstallModeType with a flag representing if the CSV supports it +// +k8s:openapi-gen=true +type InstallMode struct { + Type InstallModeType `json:"type"` + Supported bool `json:"supported"` +} + +// InstallModeSet is a mapping of unique InstallModeTypes to whether they are supported. +type InstallModeSet map[InstallModeType]bool + +// NamedInstallStrategy represents the block of an ClusterServiceVersion resource +// where the install strategy is specified. +// +k8s:openapi-gen=true +type NamedInstallStrategy struct { + StrategyName string `json:"strategy"` + StrategySpec StrategyDetailsDeployment `json:"spec,omitempty"` +} + +// StrategyDeploymentPermissions describe the rbac rules and service account needed by the install strategy +// +k8s:openapi-gen=true +type StrategyDeploymentPermissions struct { + ServiceAccountName string `json:"serviceAccountName"` + Rules []rbac.PolicyRule `json:"rules"` +} + +// StrategyDeploymentSpec contains the name, spec and labels for the deployment ALM should create +// +k8s:openapi-gen=true +type StrategyDeploymentSpec struct { + Name string `json:"name"` + Spec appsv1.DeploymentSpec `json:"spec"` + Label labels.Set `json:"label,omitempty"` +} + +// StrategyDetailsDeployment represents the parsed details of a Deployment +// InstallStrategy. +// +k8s:openapi-gen=true +type StrategyDetailsDeployment struct { + DeploymentSpecs []StrategyDeploymentSpec `json:"deployments"` + Permissions []StrategyDeploymentPermissions `json:"permissions,omitempty"` + ClusterPermissions []StrategyDeploymentPermissions `json:"clusterPermissions,omitempty"` +} + +func (d *StrategyDetailsDeployment) GetStrategyName() string { + return InstallStrategyNameDeployment +} + +// StatusDescriptor describes a field in a status block of a CRD so that OLM can consume it +// +k8s:openapi-gen=true +type StatusDescriptor struct { + Path string `json:"path"` + DisplayName string `json:"displayName,omitempty"` + Description string `json:"description,omitempty"` + XDescriptors []string `json:"x-descriptors,omitempty"` + Value json.RawMessage `json:"value,omitempty"` +} + +// SpecDescriptor describes a field in a spec block of a CRD so that OLM can consume it +// +k8s:openapi-gen=true +type SpecDescriptor struct { + Path string `json:"path"` + DisplayName string `json:"displayName,omitempty"` + Description string `json:"description,omitempty"` + XDescriptors []string `json:"x-descriptors,omitempty"` + Value json.RawMessage `json:"value,omitempty"` +} + +// ActionDescriptor describes a declarative action that can be performed on a custom resource instance +// +k8s:openapi-gen=true +type ActionDescriptor struct { + Path string `json:"path"` + DisplayName string `json:"displayName,omitempty"` + Description string `json:"description,omitempty"` + XDescriptors []string `json:"x-descriptors,omitempty"` + Value json.RawMessage `json:"value,omitempty"` +} + +// CRDDescription provides details to OLM about the CRDs +// +k8s:openapi-gen=true +type CRDDescription struct { + Name string `json:"name"` + Version string `json:"version"` + Kind string `json:"kind"` + DisplayName string `json:"displayName,omitempty"` + Description string `json:"description,omitempty"` + Resources []APIResourceReference `json:"resources,omitempty"` + StatusDescriptors []StatusDescriptor `json:"statusDescriptors,omitempty"` + SpecDescriptors []SpecDescriptor `json:"specDescriptors,omitempty"` + ActionDescriptor []ActionDescriptor `json:"actionDescriptors,omitempty"` +} + +// APIServiceDescription provides details to OLM about apis provided via aggregation +// +k8s:openapi-gen=true +type APIServiceDescription struct { + Name string `json:"name"` + Group string `json:"group"` + Version string `json:"version"` + Kind string `json:"kind"` + DeploymentName string `json:"deploymentName,omitempty"` + ContainerPort int32 `json:"containerPort,omitempty"` + DisplayName string `json:"displayName,omitempty"` + Description string `json:"description,omitempty"` + Resources []APIResourceReference `json:"resources,omitempty"` + StatusDescriptors []StatusDescriptor `json:"statusDescriptors,omitempty"` + SpecDescriptors []SpecDescriptor `json:"specDescriptors,omitempty"` + ActionDescriptor []ActionDescriptor `json:"actionDescriptors,omitempty"` +} + +// APIResourceReference is a reference to a Kubernetes resource type that the referrer utilizes. +// +k8s:openapi-gen=true +type APIResourceReference struct { + // Plural name of the referenced resource type (CustomResourceDefinition.Spec.Names[].Plural). Empty string if the referenced resource type is not a custom resource. + Name string `json:"name"` + // Kind of the referenced resource type. + Kind string `json:"kind"` + // API Version of the referenced resource type. + Version string `json:"version"` +} + +// GetName returns the name of an APIService as derived from its group and version. +func (d APIServiceDescription) GetName() string { + return fmt.Sprintf("%s.%s", d.Version, d.Group) +} + +// WebhookAdmissionType is the type of admission webhooks supported by OLM +type WebhookAdmissionType string + +const ( + // ValidatingAdmissionWebhook is for validating admission webhooks + ValidatingAdmissionWebhook WebhookAdmissionType = "ValidatingAdmissionWebhook" + // MutatingAdmissionWebhook is for mutating admission webhooks + MutatingAdmissionWebhook WebhookAdmissionType = "MutatingAdmissionWebhook" + // ConversionWebhook is for conversion webhooks + ConversionWebhook WebhookAdmissionType = "ConversionWebhook" +) + +// WebhookDescription provides details to OLM about required webhooks +// +k8s:openapi-gen=true +type WebhookDescription struct { + GenerateName string `json:"generateName"` + // +kubebuilder:validation:Enum=ValidatingAdmissionWebhook;MutatingAdmissionWebhook;ConversionWebhook + Type WebhookAdmissionType `json:"type"` + DeploymentName string `json:"deploymentName,omitempty"` + // +kubebuilder:validation:Maximum=65535 + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:default=443 + ContainerPort int32 `json:"containerPort,omitempty"` + TargetPort *intstr.IntOrString `json:"targetPort,omitempty"` + Rules []admissionregistrationv1.RuleWithOperations `json:"rules,omitempty"` + FailurePolicy *admissionregistrationv1.FailurePolicyType `json:"failurePolicy,omitempty"` + MatchPolicy *admissionregistrationv1.MatchPolicyType `json:"matchPolicy,omitempty"` + ObjectSelector *metav1.LabelSelector `json:"objectSelector,omitempty"` + SideEffects *admissionregistrationv1.SideEffectClass `json:"sideEffects"` + TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty"` + AdmissionReviewVersions []string `json:"admissionReviewVersions"` + ReinvocationPolicy *admissionregistrationv1.ReinvocationPolicyType `json:"reinvocationPolicy,omitempty"` + WebhookPath *string `json:"webhookPath,omitempty"` + ConversionCRDs []string `json:"conversionCRDs,omitempty"` +} + +// GetValidatingWebhook returns a ValidatingWebhook generated from the WebhookDescription +func (w *WebhookDescription) GetValidatingWebhook(namespace string, namespaceSelector *metav1.LabelSelector, caBundle []byte) admissionregistrationv1.ValidatingWebhook { + return admissionregistrationv1.ValidatingWebhook{ + Name: w.GenerateName, + Rules: w.Rules, + FailurePolicy: w.FailurePolicy, + MatchPolicy: w.MatchPolicy, + NamespaceSelector: namespaceSelector, + ObjectSelector: w.ObjectSelector, + SideEffects: w.SideEffects, + TimeoutSeconds: w.TimeoutSeconds, + AdmissionReviewVersions: w.AdmissionReviewVersions, + ClientConfig: admissionregistrationv1.WebhookClientConfig{ + Service: &admissionregistrationv1.ServiceReference{ + Name: w.DomainName() + "-service", + Namespace: namespace, + Path: w.WebhookPath, + Port: &w.ContainerPort, + }, + CABundle: caBundle, + }, + } +} + +// GetMutatingWebhook returns a MutatingWebhook generated from the WebhookDescription +func (w *WebhookDescription) GetMutatingWebhook(namespace string, namespaceSelector *metav1.LabelSelector, caBundle []byte) admissionregistrationv1.MutatingWebhook { + return admissionregistrationv1.MutatingWebhook{ + Name: w.GenerateName, + Rules: w.Rules, + FailurePolicy: w.FailurePolicy, + MatchPolicy: w.MatchPolicy, + NamespaceSelector: namespaceSelector, + ObjectSelector: w.ObjectSelector, + SideEffects: w.SideEffects, + TimeoutSeconds: w.TimeoutSeconds, + AdmissionReviewVersions: w.AdmissionReviewVersions, + ClientConfig: admissionregistrationv1.WebhookClientConfig{ + Service: &admissionregistrationv1.ServiceReference{ + Name: w.DomainName() + "-service", + Namespace: namespace, + Path: w.WebhookPath, + Port: &w.ContainerPort, + }, + CABundle: caBundle, + }, + ReinvocationPolicy: w.ReinvocationPolicy, + } +} + +// DomainName returns the result of replacing all periods in the given Webhook name with hyphens +func (w *WebhookDescription) DomainName() string { + // Replace all '.'s with "-"s to convert to a DNS-1035 label + return strings.Replace(w.DeploymentName, ".", "-", -1) +} + +// CustomResourceDefinitions declares all of the CRDs managed or required by +// an operator being ran by ClusterServiceVersion. +// +// If the CRD is present in the Owned list, it is implicitly required. +// +k8s:openapi-gen=true +type CustomResourceDefinitions struct { + Owned []CRDDescription `json:"owned,omitempty"` + Required []CRDDescription `json:"required,omitempty"` +} + +// APIServiceDefinitions declares all of the extension apis managed or required by +// an operator being ran by ClusterServiceVersion. +// +k8s:openapi-gen=true +type APIServiceDefinitions struct { + Owned []APIServiceDescription `json:"owned,omitempty"` + Required []APIServiceDescription `json:"required,omitempty"` +} + +// ClusterServiceVersionSpec declarations tell OLM how to install an operator +// that can manage apps for a given version. +// +k8s:openapi-gen=true +type ClusterServiceVersionSpec struct { + InstallStrategy NamedInstallStrategy `json:"install"` + Version version.OperatorVersion `json:"version,omitempty"` + // release specifies the packaging version of the operator, defaulting to empty + // release is optional + // + // A ClusterServiceVersion's release field is used to distinguish between different builds of the same operator version + // This is useful for operators that need to make changes to the CSV which don't affect their functionality, + // for example: + // - to fix a typo in their description + // - to add/amend annotations or labels + // - to amend examples or documentation + // - to produce different builds for different environments + // + // It is up to operator authors to determine the semantics of release versions they use + // for their operator. All release versions must conform to the semver prerelease format + // (dot-separated identifiers containing only alphanumerics and hyphens) and are limited + // to a maximum length of 20 characters. + // +optional + Release release.OperatorRelease `json:"release,omitzero"` + Maturity string `json:"maturity,omitempty"` + CustomResourceDefinitions CustomResourceDefinitions `json:"customresourcedefinitions,omitempty"` + APIServiceDefinitions APIServiceDefinitions `json:"apiservicedefinitions,omitempty"` + WebhookDefinitions []WebhookDescription `json:"webhookdefinitions,omitempty"` + NativeAPIs []metav1.GroupVersionKind `json:"nativeAPIs,omitempty"` + MinKubeVersion string `json:"minKubeVersion,omitempty"` + + // The name of the operator in display format. + DisplayName string `json:"displayName"` + + // Description of the operator. Can include the features, limitations or use-cases of the + // operator. + // +optional + Description string `json:"description,omitempty"` + + // A list of keywords describing the operator. + // +optional + Keywords []string `json:"keywords,omitempty"` + + // A list of organizational entities maintaining the operator. + // +optional + Maintainers []Maintainer `json:"maintainers,omitempty"` + + // The publishing entity behind the operator. + // +optional + Provider AppLink `json:"provider,omitempty"` + + // A list of links related to the operator. + // +optional + Links []AppLink `json:"links,omitempty"` + + // The icon for this operator. + // +optional + Icon []Icon `json:"icon,omitempty"` + + // InstallModes specify supported installation types + // +optional + InstallModes []InstallMode `json:"installModes,omitempty"` + + // The name of a CSV this one replaces. Should match the `metadata.Name` field of the old CSV. + // +optional + Replaces string `json:"replaces,omitempty"` + + // Map of string keys and values that can be used to organize and categorize + // (scope and select) objects. + // +optional + Labels map[string]string `json:"labels,omitempty" protobuf:"bytes,11,rep,name=labels"` + + // Annotations is an unstructured key value map stored with a resource that may be + // set by external tools to store and retrieve arbitrary metadata. + // +optional + Annotations map[string]string `json:"annotations,omitempty" protobuf:"bytes,12,rep,name=annotations"` + + // Label selector for related resources. + // +optional + Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,2,opt,name=selector"` + + // Cleanup specifies the cleanup behaviour when the CSV gets deleted + // +optional + Cleanup CleanupSpec `json:"cleanup,omitempty"` + + // The name(s) of one or more CSV(s) that should be skipped in the upgrade graph. + // Should match the `metadata.Name` field of the CSV that should be skipped. + // This field is only used during catalog creation and plays no part in cluster runtime. + // +optional + Skips []string `json:"skips,omitempty"` + + // List any related images, or other container images that your Operator might require to perform their functions. + // This list should also include operand images as well. All image references should be specified by + // digest (SHA) and not by tag. This field is only used during catalog creation and plays no part in cluster runtime. + // +optional + RelatedImages []RelatedImage `json:"relatedImages,omitempty"` +} + +// +k8s:openapi-gen=true +type CleanupSpec struct { + Enabled bool `json:"enabled"` +} + +// +k8s:openapi-gen=true +type Maintainer struct { + Name string `json:"name,omitempty"` + Email string `json:"email,omitempty"` +} + +// +k8s:openapi-gen=true +type AppLink struct { + Name string `json:"name,omitempty"` + URL string `json:"url,omitempty"` +} + +// +k8s:openapi-gen=true +type Icon struct { + Data string `json:"base64data"` + MediaType string `json:"mediatype"` +} + +// +k8s:openapi-gen=true +type RelatedImage struct { + Name string `json:"name"` + Image string `json:"image"` +} + +// ClusterServiceVersionPhase is a label for the condition of a ClusterServiceVersion at the current time. +type ClusterServiceVersionPhase string + +// These are the valid phases of ClusterServiceVersion +const ( + CSVPhaseNone = "" + // CSVPhasePending means the csv has been accepted by the system, but the install strategy has not been attempted. + // This is likely because there are unmet requirements. + CSVPhasePending ClusterServiceVersionPhase = "Pending" + // CSVPhaseInstallReady means that the requirements are met but the install strategy has not been run. + CSVPhaseInstallReady ClusterServiceVersionPhase = "InstallReady" + // CSVPhaseInstalling means that the install strategy has been initiated but not completed. + CSVPhaseInstalling ClusterServiceVersionPhase = "Installing" + // CSVPhaseSucceeded means that the resources in the CSV were created successfully. + CSVPhaseSucceeded ClusterServiceVersionPhase = "Succeeded" + // CSVPhaseFailed means that the install strategy could not be successfully completed. + CSVPhaseFailed ClusterServiceVersionPhase = "Failed" + // CSVPhaseUnknown means that for some reason the state of the csv could not be obtained. + CSVPhaseUnknown ClusterServiceVersionPhase = "Unknown" + // CSVPhaseReplacing means that a newer CSV has been created and the csv's resources will be transitioned to a new owner. + CSVPhaseReplacing ClusterServiceVersionPhase = "Replacing" + // CSVPhaseDeleting means that a CSV has been replaced by a new one and will be checked for safety before being deleted + CSVPhaseDeleting ClusterServiceVersionPhase = "Deleting" + // CSVPhaseAny matches all other phases in CSV queries + CSVPhaseAny ClusterServiceVersionPhase = "" +) + +// ConditionReason is a camelcased reason for the state transition +type ConditionReason string + +const ( + CSVReasonRequirementsUnknown ConditionReason = "RequirementsUnknown" + CSVReasonRequirementsNotMet ConditionReason = "RequirementsNotMet" + CSVReasonRequirementsMet ConditionReason = "AllRequirementsMet" + CSVReasonOwnerConflict ConditionReason = "OwnerConflict" + CSVReasonComponentFailed ConditionReason = "InstallComponentFailed" + CSVReasonComponentFailedNoRetry ConditionReason = "InstallComponentFailedNoRetry" + CSVReasonInvalidStrategy ConditionReason = "InvalidInstallStrategy" + CSVReasonWaiting ConditionReason = "InstallWaiting" + CSVReasonInstallSuccessful ConditionReason = "InstallSucceeded" + CSVReasonInstallCheckFailed ConditionReason = "InstallCheckFailed" + CSVReasonComponentUnhealthy ConditionReason = "ComponentUnhealthy" + CSVReasonBeingReplaced ConditionReason = "BeingReplaced" + CSVReasonReplaced ConditionReason = "Replaced" + CSVReasonNeedsReinstall ConditionReason = "NeedsReinstall" + CSVReasonNeedsCertRotation ConditionReason = "NeedsCertRotation" + CSVReasonAPIServiceResourceIssue ConditionReason = "APIServiceResourceIssue" + CSVReasonAPIServiceResourcesNeedReinstall ConditionReason = "APIServiceResourcesNeedReinstall" + CSVReasonAPIServiceInstallFailed ConditionReason = "APIServiceInstallFailed" + CSVReasonCopied ConditionReason = "Copied" + CSVReasonInvalidInstallModes ConditionReason = "InvalidInstallModes" + CSVReasonNoTargetNamespaces ConditionReason = "NoTargetNamespaces" + CSVReasonUnsupportedOperatorGroup ConditionReason = "UnsupportedOperatorGroup" + CSVReasonNoOperatorGroup ConditionReason = "NoOperatorGroup" + CSVReasonTooManyOperatorGroups ConditionReason = "TooManyOperatorGroups" + CSVReasonInterOperatorGroupOwnerConflict ConditionReason = "InterOperatorGroupOwnerConflict" + CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs ConditionReason = "CannotModifyStaticOperatorGroupProvidedAPIs" + CSVReasonDetectedClusterChange ConditionReason = "DetectedClusterChange" + CSVReasonInvalidWebhookDescription ConditionReason = "InvalidWebhookDescription" + CSVReasonOperatorConditionNotUpgradeable ConditionReason = "OperatorConditionNotUpgradeable" + CSVReasonWaitingForCleanupToComplete ConditionReason = "WaitingOnCleanup" +) + +// HasCaResources returns true if the CSV has owned APIServices or Webhooks. +func (c *ClusterServiceVersion) HasCAResources() bool { + // Return early if there are no owned APIServices + if len(c.Spec.APIServiceDefinitions.Owned)+len(c.Spec.WebhookDefinitions) == 0 { + return false + } + return true +} + +// Conditions appear in the status as a record of state transitions on the ClusterServiceVersion +// +k8s:openapi-gen=true +type ClusterServiceVersionCondition struct { + // Condition of the ClusterServiceVersion + Phase ClusterServiceVersionPhase `json:"phase,omitempty"` + // A human readable message indicating details about why the ClusterServiceVersion is in this condition. + // +optional + Message string `json:"message,omitempty"` + // A brief CamelCase message indicating details about why the ClusterServiceVersion is in this state. + // e.g. 'RequirementsNotMet' + // +optional + Reason ConditionReason `json:"reason,omitempty"` + // Last time we updated the status + // +optional + LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"` + // Last time the status transitioned from one status to another. + // +optional + LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty"` +} + +// OwnsCRD determines whether the current CSV owns a particular CRD. +func (csv ClusterServiceVersion) OwnsCRD(name string) bool { + for _, desc := range csv.Spec.CustomResourceDefinitions.Owned { + if desc.Name == name { + return true + } + } + + return false +} + +// OwnsAPIService determines whether the current CSV owns a particular APIService. +func (csv ClusterServiceVersion) OwnsAPIService(name string) bool { + for _, desc := range csv.Spec.APIServiceDefinitions.Owned { + apiServiceName := fmt.Sprintf("%s.%s", desc.Version, desc.Group) + if apiServiceName == name { + return true + } + } + + return false +} + +// StatusReason is a camelcased reason for the status of a RequirementStatus or DependentStatus +type StatusReason string + +const ( + RequirementStatusReasonPresent StatusReason = "Present" + RequirementStatusReasonNotPresent StatusReason = "NotPresent" + RequirementStatusReasonPresentNotSatisfied StatusReason = "PresentNotSatisfied" + // The CRD is present but the Established condition is False (not available) + RequirementStatusReasonNotAvailable StatusReason = "PresentNotAvailable" + DependentStatusReasonSatisfied StatusReason = "Satisfied" + DependentStatusReasonNotSatisfied StatusReason = "NotSatisfied" +) + +// DependentStatus is the status for a dependent requirement (to prevent infinite nesting) +// +k8s:openapi-gen=true +type DependentStatus struct { + Group string `json:"group"` + Version string `json:"version"` + Kind string `json:"kind"` + Status StatusReason `json:"status"` + UUID string `json:"uuid,omitempty"` + Message string `json:"message,omitempty"` +} + +// +k8s:openapi-gen=true +type RequirementStatus struct { + Group string `json:"group"` + Version string `json:"version"` + Kind string `json:"kind"` + Name string `json:"name"` + Status StatusReason `json:"status"` + Message string `json:"message"` + UUID string `json:"uuid,omitempty"` + Dependents []DependentStatus `json:"dependents,omitempty"` +} + +// ClusterServiceVersionStatus represents information about the status of a CSV. Status may trail the actual +// state of a system. +// +k8s:openapi-gen=true +type ClusterServiceVersionStatus struct { + // Current condition of the ClusterServiceVersion + Phase ClusterServiceVersionPhase `json:"phase,omitempty"` + // A human readable message indicating details about why the ClusterServiceVersion is in this condition. + // +optional + Message string `json:"message,omitempty"` + // A brief CamelCase message indicating details about why the ClusterServiceVersion is in this state. + // e.g. 'RequirementsNotMet' + // +optional + Reason ConditionReason `json:"reason,omitempty"` + // Last time we updated the status + // +optional + LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"` + // Last time the status transitioned from one status to another. + // +optional + LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty"` + // List of conditions, a history of state transitions + Conditions []ClusterServiceVersionCondition `json:"conditions,omitempty"` + // The status of each requirement for this CSV + RequirementStatus []RequirementStatus `json:"requirementStatus,omitempty"` + // Last time the owned APIService certs were updated + // +optional + CertsLastUpdated *metav1.Time `json:"certsLastUpdated,omitempty"` + // Time the owned APIService certs will rotate next + // +optional + CertsRotateAt *metav1.Time `json:"certsRotateAt,omitempty"` + // CleanupStatus represents information about the status of cleanup while a CSV is pending deletion + // +optional + Cleanup CleanupStatus `json:"cleanup,omitempty"` +} + +// CleanupStatus represents information about the status of cleanup while a CSV is pending deletion +// +k8s:openapi-gen=true +type CleanupStatus struct { + // PendingDeletion is the list of custom resource objects that are pending deletion and blocked on finalizers. + // This indicates the progress of cleanup that is blocking CSV deletion or operator uninstall. + // +optional + PendingDeletion []ResourceList `json:"pendingDeletion,omitempty"` +} + +// ResourceList represents a list of resources which are of the same Group/Kind +// +k8s:openapi-gen=true +type ResourceList struct { + Group string `json:"group"` + Kind string `json:"kind"` + Instances []ResourceInstance `json:"instances"` +} + +// +k8s:openapi-gen=true +type ResourceInstance struct { + Name string `json:"name"` + // Namespace can be empty for cluster-scoped resources + Namespace string `json:"namespace,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +genclient +// +kubebuilder:storageversion +// +kubebuilder:resource:shortName={csv, csvs},categories=olm +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Display",type=string,JSONPath=`.spec.displayName`,description="The name of the CSV" +// +kubebuilder:printcolumn:name="Version",type=string,JSONPath=`.spec.version`,description="The version of the CSV" +// +kubebuilder:printcolumn:name="Release",type=string,JSONPath=`.spec.release`,description="The release of this version of the CSV" +// +kubebuilder:printcolumn:name="Replaces",type=string,JSONPath=`.spec.replaces`,description="The name of a CSV that this one replaces" +// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase` + +// ClusterServiceVersion is a Custom Resource of type `ClusterServiceVersionSpec`. +type ClusterServiceVersion struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + + Spec ClusterServiceVersionSpec `json:"spec"` + // +optional + Status ClusterServiceVersionStatus `json:"status"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ClusterServiceVersionList represents a list of ClusterServiceVersions. +type ClusterServiceVersionList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []ClusterServiceVersion `json:"items"` +} + +// GetAllCRDDescriptions returns a deduplicated set of CRDDescriptions that is +// the union of the owned and required CRDDescriptions. +// +// Descriptions with the same name prefer the value in Owned. +// Descriptions are returned in alphabetical order. +func (csv ClusterServiceVersion) GetAllCRDDescriptions() []CRDDescription { + set := make(map[string]CRDDescription) + for _, required := range csv.Spec.CustomResourceDefinitions.Required { + set[required.Name] = required + } + + for _, owned := range csv.Spec.CustomResourceDefinitions.Owned { + set[owned.Name] = owned + } + + keys := make([]string, 0) + for key := range set { + keys = append(keys, key) + } + sort.StringSlice(keys).Sort() + + descs := make([]CRDDescription, 0) + for _, key := range keys { + descs = append(descs, set[key]) + } + + return descs +} + +// GetAllAPIServiceDescriptions returns a deduplicated set of APIServiceDescriptions that is +// the union of the owned and required APIServiceDescriptions. +// +// Descriptions with the same name prefer the value in Owned. +// Descriptions are returned in alphabetical order. +func (csv ClusterServiceVersion) GetAllAPIServiceDescriptions() []APIServiceDescription { + set := make(map[string]APIServiceDescription) + for _, required := range csv.Spec.APIServiceDefinitions.Required { + name := fmt.Sprintf("%s.%s", required.Version, required.Group) + set[name] = required + } + + for _, owned := range csv.Spec.APIServiceDefinitions.Owned { + name := fmt.Sprintf("%s.%s", owned.Version, owned.Group) + set[name] = owned + } + + keys := make([]string, 0) + for key := range set { + keys = append(keys, key) + } + sort.StringSlice(keys).Sort() + + descs := make([]APIServiceDescription, 0) + for _, key := range keys { + descs = append(descs, set[key]) + } + + return descs +} + +// GetRequiredAPIServiceDescriptions returns a deduplicated set of required APIServiceDescriptions +// with the intersection of required and owned removed +// Equivalent to the set subtraction required - owned +// +// Descriptions are returned in alphabetical order. +func (csv ClusterServiceVersion) GetRequiredAPIServiceDescriptions() []APIServiceDescription { + set := make(map[string]APIServiceDescription) + for _, required := range csv.Spec.APIServiceDefinitions.Required { + name := fmt.Sprintf("%s.%s", required.Version, required.Group) + set[name] = required + } + + // Remove any shared owned from the set + for _, owned := range csv.Spec.APIServiceDefinitions.Owned { + name := fmt.Sprintf("%s.%s", owned.Version, owned.Group) + if _, ok := set[name]; ok { + delete(set, name) + } + } + + keys := make([]string, 0) + for key := range set { + keys = append(keys, key) + } + sort.StringSlice(keys).Sort() + + descs := make([]APIServiceDescription, 0) + for _, key := range keys { + descs = append(descs, set[key]) + } + + return descs +} + +// GetOwnedAPIServiceDescriptions returns a deduplicated set of owned APIServiceDescriptions +// +// Descriptions are returned in alphabetical order. +func (csv ClusterServiceVersion) GetOwnedAPIServiceDescriptions() []APIServiceDescription { + set := make(map[string]APIServiceDescription) + for _, owned := range csv.Spec.APIServiceDefinitions.Owned { + name := owned.GetName() + set[name] = owned + } + + keys := make([]string, 0) + for key := range set { + keys = append(keys, key) + } + sort.StringSlice(keys).Sort() + + descs := make([]APIServiceDescription, 0) + for _, key := range keys { + descs = append(descs, set[key]) + } + + return descs +} diff --git a/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/doc.go b/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/doc.go new file mode 100644 index 000000000..fd9d1d849 --- /dev/null +++ b/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/doc.go @@ -0,0 +1,8 @@ +// +groupName=operators.coreos.com +// +k8s:deepcopy-gen=package +// +k8s:conversion-gen=github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators +// +k8s:openapi-gen=true +// +k8s:openapi-model-package=com.github.operator-framework.api.pkg.operators.v1alpha1 + +// Package v1alpha1 contains resources types for version v1alpha1 of the operators.coreos.com API group. +package v1alpha1 diff --git a/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/installplan_types.go b/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/installplan_types.go new file mode 100644 index 000000000..3b1b0feed --- /dev/null +++ b/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/installplan_types.go @@ -0,0 +1,391 @@ +package v1alpha1 + +import ( + "errors" + "fmt" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + InstallPlanKind = "InstallPlan" + InstallPlanAPIVersion = GroupName + "/" + GroupVersion +) + +// Approval is the user approval policy for an InstallPlan. +// It must be one of "Automatic" or "Manual". +type Approval string + +const ( + ApprovalAutomatic Approval = "Automatic" + ApprovalManual Approval = "Manual" +) + +// InstallPlanSpec defines a set of Application resources to be installed +type InstallPlanSpec struct { + CatalogSource string `json:"source,omitempty"` + CatalogSourceNamespace string `json:"sourceNamespace,omitempty"` + ClusterServiceVersionNames []string `json:"clusterServiceVersionNames"` + Approval Approval `json:"approval"` + Approved bool `json:"approved"` + Generation int `json:"generation,omitempty"` +} + +// InstallPlanPhase is the current status of a InstallPlan as a whole. +type InstallPlanPhase string + +const ( + InstallPlanPhaseNone InstallPlanPhase = "" + InstallPlanPhasePlanning InstallPlanPhase = "Planning" + InstallPlanPhaseRequiresApproval InstallPlanPhase = "RequiresApproval" + InstallPlanPhaseInstalling InstallPlanPhase = "Installing" + InstallPlanPhaseComplete InstallPlanPhase = "Complete" + InstallPlanPhaseFailed InstallPlanPhase = "Failed" +) + +// InstallPlanConditionType describes the state of an InstallPlan at a certain point as a whole. +type InstallPlanConditionType string + +const ( + InstallPlanResolved InstallPlanConditionType = "Resolved" + InstallPlanInstalled InstallPlanConditionType = "Installed" +) + +// ConditionReason is a camelcased reason for the state transition. +type InstallPlanConditionReason string + +const ( + InstallPlanReasonPlanUnknown InstallPlanConditionReason = "PlanUnknown" + InstallPlanReasonInstallCheckFailed InstallPlanConditionReason = "InstallCheckFailed" + InstallPlanReasonDependencyConflict InstallPlanConditionReason = "DependenciesConflict" + InstallPlanReasonComponentFailed InstallPlanConditionReason = "InstallComponentFailed" +) + +// StepStatus is the current status of a particular resource an in +// InstallPlan +type StepStatus string + +const ( + StepStatusUnknown StepStatus = "Unknown" + StepStatusNotPresent StepStatus = "NotPresent" + StepStatusPresent StepStatus = "Present" + StepStatusCreated StepStatus = "Created" + StepStatusNotCreated StepStatus = "NotCreated" + StepStatusWaitingForAPI StepStatus = "WaitingForApi" + StepStatusUnsupportedResource StepStatus = "UnsupportedResource" +) + +// ErrInvalidInstallPlan is the error returned by functions that operate on +// InstallPlans when the InstallPlan does not contain totally valid data. +var ErrInvalidInstallPlan = errors.New("the InstallPlan contains invalid data") + +// InstallPlanStatus represents the information about the status of +// steps required to complete installation. +// +// Status may trail the actual state of a system. +type InstallPlanStatus struct { + Phase InstallPlanPhase `json:"phase"` + Conditions []InstallPlanCondition `json:"conditions,omitempty"` + CatalogSources []string `json:"catalogSources"` + Plan []*Step `json:"plan,omitempty"` + // BundleLookups is the set of in-progress requests to pull and unpackage bundle content to the cluster. + // +optional + BundleLookups []BundleLookup `json:"bundleLookups,omitempty"` + // AttenuatedServiceAccountRef references the service account that is used + // to do scoped operator install. + AttenuatedServiceAccountRef *corev1.ObjectReference `json:"attenuatedServiceAccountRef,omitempty"` + + // StartTime is the time when the controller began applying + // the resources listed in the plan to the cluster. + // +optional + StartTime *metav1.Time `json:"startTime,omitempty"` + + // Message is a human-readable message containing detailed + // information that may be important to understanding why the + // plan has its current status. + // +optional + Message string `json:"message,omitempty"` +} + +// InstallPlanCondition represents the overall status of the execution of +// an InstallPlan. +type InstallPlanCondition struct { + Type InstallPlanConditionType `json:"type,omitempty"` + Status corev1.ConditionStatus `json:"status,omitempty"` // True, False, or Unknown + LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"` + LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty"` + Reason InstallPlanConditionReason `json:"reason,omitempty"` + Message string `json:"message,omitempty"` +} + +// allow overwriting `now` function for deterministic tests +var now = metav1.Now + +// GetCondition returns the InstallPlanCondition of the given type if it exists in the InstallPlanStatus' Conditions. +// Returns a condition of the given type with a ConditionStatus of "Unknown" if not found. +func (s InstallPlanStatus) GetCondition(conditionType InstallPlanConditionType) InstallPlanCondition { + for _, cond := range s.Conditions { + if cond.Type == conditionType { + return cond + } + } + + return InstallPlanCondition{ + Type: conditionType, + Status: corev1.ConditionUnknown, + } +} + +// SetCondition adds or updates a condition, using `Type` as merge key. +func (s *InstallPlanStatus) SetCondition(cond InstallPlanCondition) InstallPlanCondition { + for i, existing := range s.Conditions { + if existing.Type != cond.Type { + continue + } + if existing.Status == cond.Status { + cond.LastTransitionTime = existing.LastTransitionTime + } + s.Conditions[i] = cond + return cond + } + s.Conditions = append(s.Conditions, cond) + return cond +} + +func OrderSteps(steps []*Step) []*Step { + // CSVs must be applied first + csvList := []*Step{} + + // CRDs must be applied second + crdList := []*Step{} + + // Other resources may be applied in any order + remainingResources := []*Step{} + for _, step := range steps { + switch step.Resource.Kind { + case crdKind: + crdList = append(crdList, step) + case ClusterServiceVersionKind: + csvList = append(csvList, step) + default: + remainingResources = append(remainingResources, step) + } + } + + result := make([]*Step, len(steps)) + i := 0 + + for j := range csvList { + result[i] = csvList[j] + i++ + } + + for j := range crdList { + result[i] = crdList[j] + i++ + } + + for j := range remainingResources { + result[i] = remainingResources[j] + i++ + } + + return result +} + +func (s InstallPlanStatus) NeedsRequeue() bool { + for _, step := range s.Plan { + switch step.Status { + case StepStatusWaitingForAPI: + return true + } + } + + return false +} +func ConditionFailed(cond InstallPlanConditionType, reason InstallPlanConditionReason, message string, now *metav1.Time) InstallPlanCondition { + return InstallPlanCondition{ + Type: cond, + Status: corev1.ConditionFalse, + Reason: reason, + Message: message, + LastUpdateTime: now, + LastTransitionTime: now, + } +} + +func ConditionMet(cond InstallPlanConditionType, now *metav1.Time) InstallPlanCondition { + return InstallPlanCondition{ + Type: cond, + Status: corev1.ConditionTrue, + LastUpdateTime: now, + LastTransitionTime: now, + } +} + +// Step represents the status of an individual step in an InstallPlan. +type Step struct { + Resolving string `json:"resolving"` + Resource StepResource `json:"resource"` + Optional bool `json:"optional,omitempty"` + Status StepStatus `json:"status"` +} + +// BundleLookupConditionType is a category of the overall state of a BundleLookup. +type BundleLookupConditionType string + +const ( + // BundleLookupPending describes BundleLookups that are not complete. + BundleLookupPending BundleLookupConditionType = "BundleLookupPending" + + // BundleLookupFailed describes conditions types for when BundleLookups fail + BundleLookupFailed BundleLookupConditionType = "BundleLookupFailed" + + crdKind = "CustomResourceDefinition" +) + +type BundleLookupCondition struct { + // Type of condition. + Type BundleLookupConditionType `json:"type"` + // Status of the condition, one of True, False, Unknown. + Status corev1.ConditionStatus `json:"status"` + // The reason for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty"` + // A human readable message indicating details about the transition. + // +optional + Message string `json:"message,omitempty"` + // Last time the condition was probed. + // +optional + LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"` + // Last time the condition transitioned from one status to another. + // +optional + LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty"` +} + +// BundleLookup is a request to pull and unpackage the content of a bundle to the cluster. +type BundleLookup struct { + // Path refers to the location of a bundle to pull. + // It's typically an image reference. + Path string `json:"path"` + // Identifier is the catalog-unique name of the operator (the name of the CSV for bundles that contain CSVs) + Identifier string `json:"identifier"` + // Replaces is the name of the bundle to replace with the one found at Path. + Replaces string `json:"replaces"` + // CatalogSourceRef is a reference to the CatalogSource the bundle path was resolved from. + CatalogSourceRef *corev1.ObjectReference `json:"catalogSourceRef"` + // Conditions represents the overall state of a BundleLookup. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + Conditions []BundleLookupCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + // The effective properties of the unpacked bundle. + // +optional + Properties string `json:"properties,omitempty"` +} + +// GetCondition returns the BundleLookupCondition of the given type if it exists in the BundleLookup's Conditions. +// Returns a condition of the given type with a ConditionStatus of "Unknown" if not found. +func (b BundleLookup) GetCondition(conditionType BundleLookupConditionType) BundleLookupCondition { + for _, cond := range b.Conditions { + if cond.Type == conditionType { + return cond + } + } + + return BundleLookupCondition{ + Type: conditionType, + Status: corev1.ConditionUnknown, + } +} + +// RemoveCondition removes the BundleLookupCondition of the given type from the BundleLookup's Conditions if it exists. +func (b *BundleLookup) RemoveCondition(conditionType BundleLookupConditionType) { + for i, cond := range b.Conditions { + if cond.Type == conditionType { + b.Conditions = append(b.Conditions[:i], b.Conditions[i+1:]...) + if len(b.Conditions) == 0 { + b.Conditions = nil + } + return + } + } +} + +// SetCondition replaces the existing BundleLookupCondition of the same type, or adds it if it was not found. +func (b *BundleLookup) SetCondition(cond BundleLookupCondition) BundleLookupCondition { + for i, existing := range b.Conditions { + if existing.Type != cond.Type { + continue + } + if existing.Status == cond.Status { + cond.LastTransitionTime = existing.LastTransitionTime + } + b.Conditions[i] = cond + return cond + } + b.Conditions = append(b.Conditions, cond) + + return cond +} + +func (s *Step) String() string { + return fmt.Sprintf("%s: %s (%s)", s.Resolving, s.Resource, s.Status) +} + +// StepResource represents the status of a resource to be tracked by an +// InstallPlan. +type StepResource struct { + CatalogSource string `json:"sourceName"` + CatalogSourceNamespace string `json:"sourceNamespace"` + Group string `json:"group"` + Version string `json:"version"` + Kind string `json:"kind"` + Name string `json:"name"` + Manifest string `json:"manifest,omitempty"` +} + +func (r StepResource) String() string { + return fmt.Sprintf("%s[%s/%s/%s (%s/%s)]", r.Name, r.Group, r.Version, r.Kind, r.CatalogSource, r.CatalogSourceNamespace) +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +genclient +// +kubebuilder:resource:shortName=ip,categories=olm +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="CSV",type=string,JSONPath=`.spec.clusterServiceVersionNames[0]`,description="The first CSV in the list of clusterServiceVersionNames" +// +kubebuilder:printcolumn:name="Approval",type=string,JSONPath=`.spec.approval`,description="The approval mode" +// +kubebuilder:printcolumn:name="Approved",type=boolean,JSONPath=`.spec.approved` + +// InstallPlan defines the installation of a set of operators. +type InstallPlan struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + + Spec InstallPlanSpec `json:"spec"` + // +optional + Status InstallPlanStatus `json:"status"` +} + +// EnsureCatalogSource ensures that a CatalogSource is present in the Status +// block of an InstallPlan. +func (p *InstallPlan) EnsureCatalogSource(sourceName string) { + for _, srcName := range p.Status.CatalogSources { + if srcName == sourceName { + return + } + } + + p.Status.CatalogSources = append(p.Status.CatalogSources, sourceName) +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// InstallPlanList is a list of InstallPlan resources. +type InstallPlanList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []InstallPlan `json:"items"` +} diff --git a/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/register.go b/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/register.go new file mode 100644 index 000000000..f1cd86f1a --- /dev/null +++ b/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/register.go @@ -0,0 +1,55 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/operator-framework/api/pkg/operators" +) + +const ( + // GroupName is the group name used in this package. + GroupName = operators.GroupName + // GroupVersion is the group version used in this package. + GroupVersion = "v1alpha1" +) + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: GroupVersion} + +// Kind takes an unqualified kind and returns back a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + // SchemeBuilder initializes a scheme builder + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + // AddToScheme is a global function that registers this API group & version to a scheme + AddToScheme = SchemeBuilder.AddToScheme + + // localSchemeBuilder is expected by generated conversion functions + localSchemeBuilder = &SchemeBuilder +) + +// addKnownTypes adds the list of known types to Scheme +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &CatalogSource{}, + &CatalogSourceList{}, + &InstallPlan{}, + &InstallPlanList{}, + &Subscription{}, + &SubscriptionList{}, + &ClusterServiceVersion{}, + &ClusterServiceVersionList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/subscription_types.go b/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/subscription_types.go new file mode 100644 index 000000000..292fedf9b --- /dev/null +++ b/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/subscription_types.go @@ -0,0 +1,360 @@ +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +const ( + SubscriptionKind = "Subscription" + SubscriptionCRDAPIVersion = GroupName + "/" + GroupVersion +) + +// SubscriptionState tracks when updates are available, installing, or service is up to date +type SubscriptionState string + +const ( + SubscriptionStateNone = "" + SubscriptionStateFailed = "UpgradeFailed" + SubscriptionStateUpgradeAvailable = "UpgradeAvailable" + SubscriptionStateUpgradePending = "UpgradePending" + SubscriptionStateAtLatest = "AtLatestKnown" +) + +const ( + SubscriptionReasonInvalidCatalog ConditionReason = "InvalidCatalog" + SubscriptionReasonUpgradeSucceeded ConditionReason = "UpgradeSucceeded" +) + +// SubscriptionSpec defines an Application that can be installed +type SubscriptionSpec struct { + CatalogSource string `json:"source"` + CatalogSourceNamespace string `json:"sourceNamespace"` + Package string `json:"name"` + Channel string `json:"channel,omitempty"` + StartingCSV string `json:"startingCSV,omitempty"` + InstallPlanApproval Approval `json:"installPlanApproval,omitempty"` + Config *SubscriptionConfig `json:"config,omitempty"` +} + +// SubscriptionConfig contains configuration specified for a subscription. +type SubscriptionConfig struct { + // Selector is the label selector for pods to be configured. + // Existing ReplicaSets whose pods are + // selected by this will be the ones affected by this deployment. + // It must match the pod template's labels. + Selector *metav1.LabelSelector `json:"selector,omitempty"` + + // NodeSelector is a selector which must be true for the pod to fit on a node. + // Selector which must match a node's labels for the pod to be scheduled on that node. + // More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + // +optional + NodeSelector map[string]string `json:"nodeSelector,omitempty"` + + // Tolerations are the pod's tolerations. + // +optional + Tolerations []corev1.Toleration `json:"tolerations,omitempty"` + + // Resources represents compute resources required by this container. + // Immutable. + // More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/ + // +optional + Resources *corev1.ResourceRequirements `json:"resources,omitempty"` + + // EnvFrom is a list of sources to populate environment variables in the container. + // The keys defined within a source must be a C_IDENTIFIER. All invalid keys + // will be reported as an event when the container is starting. When a key exists in multiple + // sources, the value associated with the last source will take precedence. + // Values defined by an Env with a duplicate key will take precedence. + // Immutable. + // +optional + EnvFrom []corev1.EnvFromSource `json:"envFrom,omitempty"` + // Env is a list of environment variables to set in the container. + // Cannot be updated. + // +patchMergeKey=name + // +patchStrategy=merge + // +optional + Env []corev1.EnvVar `json:"env,omitempty" patchMergeKey:"name" patchStrategy:"merge"` + + // List of Volumes to set in the podSpec. + // +optional + Volumes []corev1.Volume `json:"volumes,omitempty"` + + // List of VolumeMounts to set in the container. + // +optional + VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"` + + // If specified, overrides the pod's scheduling constraints. + // nil sub-attributes will *not* override the original values in the pod.spec for those sub-attributes. + // Use empty object ({}) to erase original sub-attribute values. + // +optional + Affinity *corev1.Affinity `json:"affinity,omitempty" protobuf:"bytes,18,opt,name=affinity"` + + // Annotations is an unstructured key value map stored with each Deployment, Pod, APIService in the Operator. + // Typically, annotations may be set by external tools to store and retrieve arbitrary metadata. + // Use this field to pre-define annotations that OLM should add to each of the Subscription's + // deployments, pods, and apiservices. + // +optional + Annotations map[string]string `json:"annotations,omitempty" protobuf:"bytes,12,rep,name=annotations"` +} + +// SubscriptionConditionType indicates an explicit state condition about a Subscription in "abnormal-true" +// polarity form (see https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties). +type SubscriptionConditionType string + +const ( + // SubscriptionCatalogSourcesUnhealthy indicates that some or all of the CatalogSources to be used in resolution are unhealthy. + SubscriptionCatalogSourcesUnhealthy SubscriptionConditionType = "CatalogSourcesUnhealthy" + + // SubscriptionInstallPlanMissing indicates that a Subscription's InstallPlan is missing. + SubscriptionInstallPlanMissing SubscriptionConditionType = "InstallPlanMissing" + + // SubscriptionInstallPlanPending indicates that a Subscription's InstallPlan is pending installation. + SubscriptionInstallPlanPending SubscriptionConditionType = "InstallPlanPending" + + // SubscriptionInstallPlanFailed indicates that the installation of a Subscription's InstallPlan has failed. + SubscriptionInstallPlanFailed SubscriptionConditionType = "InstallPlanFailed" + + // SubscriptionResolutionFailed indicates that the dependency resolution in the namespace in which the subscription is created has failed + SubscriptionResolutionFailed SubscriptionConditionType = "ResolutionFailed" + + // SubscriptionBundleUnpacking indicates that the unpack job is currently running + SubscriptionBundleUnpacking SubscriptionConditionType = "BundleUnpacking" + + // SubscriptionBundleUnpackFailed indicates that the unpack job failed + SubscriptionBundleUnpackFailed SubscriptionConditionType = "BundleUnpackFailed" + + // SubscriptionDeprecated is a roll-up condition which indicates that the Operator currently installed with this Subscription + //has been deprecated. It will be present when any of the three deprecation types (Package, Channel, Bundle) are present. + SubscriptionDeprecated SubscriptionConditionType = "Deprecated" + + // SubscriptionOperatorDeprecated indicates that the Package currently installed with this Subscription has been deprecated. + SubscriptionPackageDeprecated SubscriptionConditionType = "PackageDeprecated" + + // SubscriptionOperatorDeprecated indicates that the Channel used with this Subscription has been deprecated. + SubscriptionChannelDeprecated SubscriptionConditionType = "ChannelDeprecated" + + // SubscriptionOperatorDeprecated indicates that the Bundle currently installed with this Subscription has been deprecated. + SubscriptionBundleDeprecated SubscriptionConditionType = "BundleDeprecated" +) + +const ( + // NoCatalogSourcesFound is a reason string for Subscriptions with unhealthy CatalogSources due to none being available. + NoCatalogSourcesFound = "NoCatalogSourcesFound" + + // AllCatalogSourcesHealthy is a reason string for Subscriptions that transitioned due to all CatalogSources being healthy. + AllCatalogSourcesHealthy = "AllCatalogSourcesHealthy" + + // CatalogSourcesAdded is a reason string for Subscriptions that transitioned due to CatalogSources being added. + CatalogSourcesAdded = "CatalogSourcesAdded" + + // CatalogSourcesUpdated is a reason string for Subscriptions that transitioned due to CatalogSource being updated. + CatalogSourcesUpdated = "CatalogSourcesUpdated" + + // CatalogSourcesDeleted is a reason string for Subscriptions that transitioned due to CatalogSources being removed. + CatalogSourcesDeleted = "CatalogSourcesDeleted" + + // UnhealthyCatalogSourceFound is a reason string for Subscriptions that transitioned because an unhealthy CatalogSource was found. + UnhealthyCatalogSourceFound = "UnhealthyCatalogSourceFound" + + // ReferencedInstallPlanNotFound is a reason string for Subscriptions that transitioned due to a referenced InstallPlan not being found. + ReferencedInstallPlanNotFound = "ReferencedInstallPlanNotFound" + + // InstallPlanNotYetReconciled is a reason string for Subscriptions that transitioned due to a referenced InstallPlan not being reconciled yet. + InstallPlanNotYetReconciled = "InstallPlanNotYetReconciled" + + // InstallPlanFailed is a reason string for Subscriptions that transitioned due to a referenced InstallPlan failing without setting an explicit failure condition. + InstallPlanFailed = "InstallPlanFailed" +) + +// SubscriptionCondition represents the latest available observations of a Subscription's state. +type SubscriptionCondition struct { + // Type is the type of Subscription condition. + Type SubscriptionConditionType `json:"type" description:"type of Subscription condition"` + + // Status is the status of the condition, one of True, False, Unknown. + Status corev1.ConditionStatus `json:"status" description:"status of the condition, one of True, False, Unknown"` + + // Reason is a one-word CamelCase reason for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty" description:"one-word CamelCase reason for the condition's last transition"` + + // Message is a human-readable message indicating details about last transition. + // +optional + Message string `json:"message,omitempty" description:"human-readable message indicating details about last transition"` + + // LastHeartbeatTime is the last time we got an update on a given condition + // +optional + LastHeartbeatTime *metav1.Time `json:"lastHeartbeatTime,omitempty" description:"last time we got an update on a given condition"` + + // LastTransitionTime is the last time the condition transit from one status to another + // +optional + LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty" description:"last time the condition transit from one status to another" hash:"ignore"` +} + +// Equals returns true if a SubscriptionCondition equals the one given, false otherwise. +// Equality is determined by the equality of the type, status, reason, and message fields ONLY. +func (s SubscriptionCondition) Equals(condition SubscriptionCondition) bool { + return s.Type == condition.Type && s.Status == condition.Status && s.Reason == condition.Reason && s.Message == condition.Message +} + +type SubscriptionStatus struct { + // CurrentCSV is the CSV the Subscription is progressing to. + // +optional + CurrentCSV string `json:"currentCSV,omitempty"` + + // InstalledCSV is the CSV currently installed by the Subscription. + // +optional + InstalledCSV string `json:"installedCSV,omitempty"` + + // Install is a reference to the latest InstallPlan generated for the Subscription. + // DEPRECATED: InstallPlanRef + // +optional + Install *InstallPlanReference `json:"installplan,omitempty"` + + // State represents the current state of the Subscription + // +optional + State SubscriptionState `json:"state,omitempty"` + + // Reason is the reason the Subscription was transitioned to its current state. + // +optional + Reason ConditionReason `json:"reason,omitempty"` + + // InstallPlanGeneration is the current generation of the installplan + // +optional + InstallPlanGeneration int `json:"installPlanGeneration,omitempty"` + + // InstallPlanRef is a reference to the latest InstallPlan that contains the Subscription's current CSV. + // +optional + InstallPlanRef *corev1.ObjectReference `json:"installPlanRef,omitempty"` + + // CatalogHealth contains the Subscription's view of its relevant CatalogSources' status. + // It is used to determine SubscriptionStatusConditions related to CatalogSources. + // +optional + CatalogHealth []SubscriptionCatalogHealth `json:"catalogHealth,omitempty"` + + // Conditions is a list of the latest available observations about a Subscription's current state. + // +optional + Conditions []SubscriptionCondition `json:"conditions,omitempty" hash:"set"` + + // LastUpdated represents the last time that the Subscription status was updated. + LastUpdated metav1.Time `json:"lastUpdated"` +} + +// GetCondition returns the SubscriptionCondition of the given type if it exists in the SubscriptionStatus' Conditions. +// Returns a condition of the given type with a ConditionStatus of "Unknown" if not found. +func (s SubscriptionStatus) GetCondition(conditionType SubscriptionConditionType) SubscriptionCondition { + for _, cond := range s.Conditions { + if cond.Type == conditionType { + return cond + } + } + + return SubscriptionCondition{ + Type: conditionType, + Status: corev1.ConditionUnknown, + } +} + +// SetCondition sets the given SubscriptionCondition in the SubscriptionStatus' Conditions. +func (s *SubscriptionStatus) SetCondition(condition SubscriptionCondition) { + for i, cond := range s.Conditions { + if cond.Type == condition.Type { + s.Conditions[i] = condition + return + } + } + + s.Conditions = append(s.Conditions, condition) +} + +// RemoveConditions removes any conditions of the given types from the SubscriptionStatus' Conditions. +func (s *SubscriptionStatus) RemoveConditions(remove ...SubscriptionConditionType) { + exclusions := map[SubscriptionConditionType]struct{}{} + for _, r := range remove { + exclusions[r] = struct{}{} + } + + var filtered []SubscriptionCondition + for _, cond := range s.Conditions { + if _, ok := exclusions[cond.Type]; ok { + // Skip excluded condition types + continue + } + filtered = append(filtered, cond) + } + + s.Conditions = filtered +} + +type InstallPlanReference struct { + APIVersion string `json:"apiVersion"` + Kind string `json:"kind"` + Name string `json:"name"` + UID types.UID `json:"uuid"` +} + +// SubscriptionCatalogHealth describes the health of a CatalogSource the Subscription knows about. +type SubscriptionCatalogHealth struct { + // CatalogSourceRef is a reference to a CatalogSource. + CatalogSourceRef *corev1.ObjectReference `json:"catalogSourceRef"` + + // LastUpdated represents the last time that the CatalogSourceHealth changed + LastUpdated *metav1.Time `json:"lastUpdated"` + + // Healthy is true if the CatalogSource is healthy; false otherwise. + Healthy bool `json:"healthy"` +} + +// Equals returns true if a SubscriptionCatalogHealth equals the one given, false otherwise. +// Equality is based SOLEY on health and UID. +func (s SubscriptionCatalogHealth) Equals(health SubscriptionCatalogHealth) bool { + return s.Healthy == health.Healthy && s.CatalogSourceRef.UID == health.CatalogSourceRef.UID +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +genclient +// +kubebuilder:resource:shortName={sub, subs},categories=olm +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Package",type=string,JSONPath=`.spec.name`,description="The package subscribed to" +// +kubebuilder:printcolumn:name="Source",type=string,JSONPath=`.spec.source`,description="The catalog source for the specified package" +// +kubebuilder:printcolumn:name="Channel",type=string,JSONPath=`.spec.channel`,description="The channel of updates to subscribe to" + +// Subscription keeps operators up to date by tracking changes to Catalogs. +type Subscription struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + + Spec *SubscriptionSpec `json:"spec"` + // +optional + Status SubscriptionStatus `json:"status"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// SubscriptionList is a list of Subscription resources. +type SubscriptionList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []Subscription `json:"items"` +} + +// GetInstallPlanApproval gets the configured install plan approval or the default +func (s *Subscription) GetInstallPlanApproval() Approval { + if s.Spec.InstallPlanApproval == ApprovalManual { + return ApprovalManual + } + return ApprovalAutomatic +} + +// NewInstallPlanReference returns an InstallPlanReference for the given ObjectReference. +func NewInstallPlanReference(ref *corev1.ObjectReference) *InstallPlanReference { + return &InstallPlanReference{ + APIVersion: ref.APIVersion, + Kind: ref.Kind, + Name: ref.Name, + UID: ref.UID, + } +} diff --git a/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/zz_generated.deepcopy.go b/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 000000000..685fa26a3 --- /dev/null +++ b/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,1633 @@ +//go:build !ignore_autogenerated + +/* + +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. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "encoding/json" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *APIResourceReference) DeepCopyInto(out *APIResourceReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIResourceReference. +func (in *APIResourceReference) DeepCopy() *APIResourceReference { + if in == nil { + return nil + } + out := new(APIResourceReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *APIServiceDefinitions) DeepCopyInto(out *APIServiceDefinitions) { + *out = *in + if in.Owned != nil { + in, out := &in.Owned, &out.Owned + *out = make([]APIServiceDescription, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Required != nil { + in, out := &in.Required, &out.Required + *out = make([]APIServiceDescription, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIServiceDefinitions. +func (in *APIServiceDefinitions) DeepCopy() *APIServiceDefinitions { + if in == nil { + return nil + } + out := new(APIServiceDefinitions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *APIServiceDescription) DeepCopyInto(out *APIServiceDescription) { + *out = *in + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = make([]APIResourceReference, len(*in)) + copy(*out, *in) + } + if in.StatusDescriptors != nil { + in, out := &in.StatusDescriptors, &out.StatusDescriptors + *out = make([]StatusDescriptor, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.SpecDescriptors != nil { + in, out := &in.SpecDescriptors, &out.SpecDescriptors + *out = make([]SpecDescriptor, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ActionDescriptor != nil { + in, out := &in.ActionDescriptor, &out.ActionDescriptor + *out = make([]ActionDescriptor, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIServiceDescription. +func (in *APIServiceDescription) DeepCopy() *APIServiceDescription { + if in == nil { + return nil + } + out := new(APIServiceDescription) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ActionDescriptor) DeepCopyInto(out *ActionDescriptor) { + *out = *in + if in.XDescriptors != nil { + in, out := &in.XDescriptors, &out.XDescriptors + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Value != nil { + in, out := &in.Value, &out.Value + *out = make(json.RawMessage, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ActionDescriptor. +func (in *ActionDescriptor) DeepCopy() *ActionDescriptor { + if in == nil { + return nil + } + out := new(ActionDescriptor) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AppLink) DeepCopyInto(out *AppLink) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppLink. +func (in *AppLink) DeepCopy() *AppLink { + if in == nil { + return nil + } + out := new(AppLink) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BundleLookup) DeepCopyInto(out *BundleLookup) { + *out = *in + if in.CatalogSourceRef != nil { + in, out := &in.CatalogSourceRef, &out.CatalogSourceRef + *out = new(v1.ObjectReference) + **out = **in + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]BundleLookupCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BundleLookup. +func (in *BundleLookup) DeepCopy() *BundleLookup { + if in == nil { + return nil + } + out := new(BundleLookup) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BundleLookupCondition) DeepCopyInto(out *BundleLookupCondition) { + *out = *in + if in.LastUpdateTime != nil { + in, out := &in.LastUpdateTime, &out.LastUpdateTime + *out = (*in).DeepCopy() + } + if in.LastTransitionTime != nil { + in, out := &in.LastTransitionTime, &out.LastTransitionTime + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BundleLookupCondition. +func (in *BundleLookupCondition) DeepCopy() *BundleLookupCondition { + if in == nil { + return nil + } + out := new(BundleLookupCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CRDDescription) DeepCopyInto(out *CRDDescription) { + *out = *in + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = make([]APIResourceReference, len(*in)) + copy(*out, *in) + } + if in.StatusDescriptors != nil { + in, out := &in.StatusDescriptors, &out.StatusDescriptors + *out = make([]StatusDescriptor, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.SpecDescriptors != nil { + in, out := &in.SpecDescriptors, &out.SpecDescriptors + *out = make([]SpecDescriptor, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ActionDescriptor != nil { + in, out := &in.ActionDescriptor, &out.ActionDescriptor + *out = make([]ActionDescriptor, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CRDDescription. +func (in *CRDDescription) DeepCopy() *CRDDescription { + if in == nil { + return nil + } + out := new(CRDDescription) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CatalogSource) DeepCopyInto(out *CatalogSource) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CatalogSource. +func (in *CatalogSource) DeepCopy() *CatalogSource { + if in == nil { + return nil + } + out := new(CatalogSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CatalogSource) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CatalogSourceList) DeepCopyInto(out *CatalogSourceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]CatalogSource, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CatalogSourceList. +func (in *CatalogSourceList) DeepCopy() *CatalogSourceList { + if in == nil { + return nil + } + out := new(CatalogSourceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CatalogSourceList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CatalogSourceSpec) DeepCopyInto(out *CatalogSourceSpec) { + *out = *in + if in.GrpcPodConfig != nil { + in, out := &in.GrpcPodConfig, &out.GrpcPodConfig + *out = new(GrpcPodConfig) + (*in).DeepCopyInto(*out) + } + if in.UpdateStrategy != nil { + in, out := &in.UpdateStrategy, &out.UpdateStrategy + *out = new(UpdateStrategy) + (*in).DeepCopyInto(*out) + } + if in.Secrets != nil { + in, out := &in.Secrets, &out.Secrets + *out = make([]string, len(*in)) + copy(*out, *in) + } + out.Icon = in.Icon +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CatalogSourceSpec. +func (in *CatalogSourceSpec) DeepCopy() *CatalogSourceSpec { + if in == nil { + return nil + } + out := new(CatalogSourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CatalogSourceStatus) DeepCopyInto(out *CatalogSourceStatus) { + *out = *in + if in.LatestImageRegistryPoll != nil { + in, out := &in.LatestImageRegistryPoll, &out.LatestImageRegistryPoll + *out = (*in).DeepCopy() + } + if in.ConfigMapResource != nil { + in, out := &in.ConfigMapResource, &out.ConfigMapResource + *out = new(ConfigMapResourceReference) + (*in).DeepCopyInto(*out) + } + if in.RegistryServiceStatus != nil { + in, out := &in.RegistryServiceStatus, &out.RegistryServiceStatus + *out = new(RegistryServiceStatus) + (*in).DeepCopyInto(*out) + } + if in.GRPCConnectionState != nil { + in, out := &in.GRPCConnectionState, &out.GRPCConnectionState + *out = new(GRPCConnectionState) + (*in).DeepCopyInto(*out) + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CatalogSourceStatus. +func (in *CatalogSourceStatus) DeepCopy() *CatalogSourceStatus { + if in == nil { + return nil + } + out := new(CatalogSourceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CleanupSpec) DeepCopyInto(out *CleanupSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CleanupSpec. +func (in *CleanupSpec) DeepCopy() *CleanupSpec { + if in == nil { + return nil + } + out := new(CleanupSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CleanupStatus) DeepCopyInto(out *CleanupStatus) { + *out = *in + if in.PendingDeletion != nil { + in, out := &in.PendingDeletion, &out.PendingDeletion + *out = make([]ResourceList, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CleanupStatus. +func (in *CleanupStatus) DeepCopy() *CleanupStatus { + if in == nil { + return nil + } + out := new(CleanupStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterServiceVersion) DeepCopyInto(out *ClusterServiceVersion) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterServiceVersion. +func (in *ClusterServiceVersion) DeepCopy() *ClusterServiceVersion { + if in == nil { + return nil + } + out := new(ClusterServiceVersion) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterServiceVersion) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterServiceVersionCondition) DeepCopyInto(out *ClusterServiceVersionCondition) { + *out = *in + if in.LastUpdateTime != nil { + in, out := &in.LastUpdateTime, &out.LastUpdateTime + *out = (*in).DeepCopy() + } + if in.LastTransitionTime != nil { + in, out := &in.LastTransitionTime, &out.LastTransitionTime + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterServiceVersionCondition. +func (in *ClusterServiceVersionCondition) DeepCopy() *ClusterServiceVersionCondition { + if in == nil { + return nil + } + out := new(ClusterServiceVersionCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterServiceVersionList) DeepCopyInto(out *ClusterServiceVersionList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ClusterServiceVersion, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterServiceVersionList. +func (in *ClusterServiceVersionList) DeepCopy() *ClusterServiceVersionList { + if in == nil { + return nil + } + out := new(ClusterServiceVersionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterServiceVersionList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterServiceVersionSpec) DeepCopyInto(out *ClusterServiceVersionSpec) { + *out = *in + in.InstallStrategy.DeepCopyInto(&out.InstallStrategy) + in.Version.DeepCopyInto(&out.Version) + in.Release.DeepCopyInto(&out.Release) + in.CustomResourceDefinitions.DeepCopyInto(&out.CustomResourceDefinitions) + in.APIServiceDefinitions.DeepCopyInto(&out.APIServiceDefinitions) + if in.WebhookDefinitions != nil { + in, out := &in.WebhookDefinitions, &out.WebhookDefinitions + *out = make([]WebhookDescription, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.NativeAPIs != nil { + in, out := &in.NativeAPIs, &out.NativeAPIs + *out = make([]metav1.GroupVersionKind, len(*in)) + copy(*out, *in) + } + if in.Keywords != nil { + in, out := &in.Keywords, &out.Keywords + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Maintainers != nil { + in, out := &in.Maintainers, &out.Maintainers + *out = make([]Maintainer, len(*in)) + copy(*out, *in) + } + out.Provider = in.Provider + if in.Links != nil { + in, out := &in.Links, &out.Links + *out = make([]AppLink, len(*in)) + copy(*out, *in) + } + if in.Icon != nil { + in, out := &in.Icon, &out.Icon + *out = make([]Icon, len(*in)) + copy(*out, *in) + } + if in.InstallModes != nil { + in, out := &in.InstallModes, &out.InstallModes + *out = make([]InstallMode, len(*in)) + copy(*out, *in) + } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + *out = new(metav1.LabelSelector) + (*in).DeepCopyInto(*out) + } + out.Cleanup = in.Cleanup + if in.Skips != nil { + in, out := &in.Skips, &out.Skips + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.RelatedImages != nil { + in, out := &in.RelatedImages, &out.RelatedImages + *out = make([]RelatedImage, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterServiceVersionSpec. +func (in *ClusterServiceVersionSpec) DeepCopy() *ClusterServiceVersionSpec { + if in == nil { + return nil + } + out := new(ClusterServiceVersionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterServiceVersionStatus) DeepCopyInto(out *ClusterServiceVersionStatus) { + *out = *in + if in.LastUpdateTime != nil { + in, out := &in.LastUpdateTime, &out.LastUpdateTime + *out = (*in).DeepCopy() + } + if in.LastTransitionTime != nil { + in, out := &in.LastTransitionTime, &out.LastTransitionTime + *out = (*in).DeepCopy() + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]ClusterServiceVersionCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.RequirementStatus != nil { + in, out := &in.RequirementStatus, &out.RequirementStatus + *out = make([]RequirementStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.CertsLastUpdated != nil { + in, out := &in.CertsLastUpdated, &out.CertsLastUpdated + *out = (*in).DeepCopy() + } + if in.CertsRotateAt != nil { + in, out := &in.CertsRotateAt, &out.CertsRotateAt + *out = (*in).DeepCopy() + } + in.Cleanup.DeepCopyInto(&out.Cleanup) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterServiceVersionStatus. +func (in *ClusterServiceVersionStatus) DeepCopy() *ClusterServiceVersionStatus { + if in == nil { + return nil + } + out := new(ClusterServiceVersionStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConfigMapResourceReference) DeepCopyInto(out *ConfigMapResourceReference) { + *out = *in + in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapResourceReference. +func (in *ConfigMapResourceReference) DeepCopy() *ConfigMapResourceReference { + if in == nil { + return nil + } + out := new(ConfigMapResourceReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CustomResourceDefinitions) DeepCopyInto(out *CustomResourceDefinitions) { + *out = *in + if in.Owned != nil { + in, out := &in.Owned, &out.Owned + *out = make([]CRDDescription, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Required != nil { + in, out := &in.Required, &out.Required + *out = make([]CRDDescription, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceDefinitions. +func (in *CustomResourceDefinitions) DeepCopy() *CustomResourceDefinitions { + if in == nil { + return nil + } + out := new(CustomResourceDefinitions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DependentStatus) DeepCopyInto(out *DependentStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DependentStatus. +func (in *DependentStatus) DeepCopy() *DependentStatus { + if in == nil { + return nil + } + out := new(DependentStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtractContentConfig) DeepCopyInto(out *ExtractContentConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtractContentConfig. +func (in *ExtractContentConfig) DeepCopy() *ExtractContentConfig { + if in == nil { + return nil + } + out := new(ExtractContentConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GRPCConnectionState) DeepCopyInto(out *GRPCConnectionState) { + *out = *in + in.LastConnectTime.DeepCopyInto(&out.LastConnectTime) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GRPCConnectionState. +func (in *GRPCConnectionState) DeepCopy() *GRPCConnectionState { + if in == nil { + return nil + } + out := new(GRPCConnectionState) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GrpcPodConfig) DeepCopyInto(out *GrpcPodConfig) { + *out = *in + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = make([]v1.Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Affinity != nil { + in, out := &in.Affinity, &out.Affinity + *out = new(v1.Affinity) + (*in).DeepCopyInto(*out) + } + if in.PriorityClassName != nil { + in, out := &in.PriorityClassName, &out.PriorityClassName + *out = new(string) + **out = **in + } + if in.MemoryTarget != nil { + in, out := &in.MemoryTarget, &out.MemoryTarget + x := (*in).DeepCopy() + *out = &x + } + if in.ExtractContent != nil { + in, out := &in.ExtractContent, &out.ExtractContent + *out = new(ExtractContentConfig) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrpcPodConfig. +func (in *GrpcPodConfig) DeepCopy() *GrpcPodConfig { + if in == nil { + return nil + } + out := new(GrpcPodConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Icon) DeepCopyInto(out *Icon) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Icon. +func (in *Icon) DeepCopy() *Icon { + if in == nil { + return nil + } + out := new(Icon) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InstallMode) DeepCopyInto(out *InstallMode) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstallMode. +func (in *InstallMode) DeepCopy() *InstallMode { + if in == nil { + return nil + } + out := new(InstallMode) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in InstallModeSet) DeepCopyInto(out *InstallModeSet) { + { + in := &in + *out = make(InstallModeSet, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstallModeSet. +func (in InstallModeSet) DeepCopy() InstallModeSet { + if in == nil { + return nil + } + out := new(InstallModeSet) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InstallPlan) DeepCopyInto(out *InstallPlan) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstallPlan. +func (in *InstallPlan) DeepCopy() *InstallPlan { + if in == nil { + return nil + } + out := new(InstallPlan) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *InstallPlan) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InstallPlanCondition) DeepCopyInto(out *InstallPlanCondition) { + *out = *in + if in.LastUpdateTime != nil { + in, out := &in.LastUpdateTime, &out.LastUpdateTime + *out = (*in).DeepCopy() + } + if in.LastTransitionTime != nil { + in, out := &in.LastTransitionTime, &out.LastTransitionTime + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstallPlanCondition. +func (in *InstallPlanCondition) DeepCopy() *InstallPlanCondition { + if in == nil { + return nil + } + out := new(InstallPlanCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InstallPlanList) DeepCopyInto(out *InstallPlanList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]InstallPlan, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstallPlanList. +func (in *InstallPlanList) DeepCopy() *InstallPlanList { + if in == nil { + return nil + } + out := new(InstallPlanList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *InstallPlanList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InstallPlanReference) DeepCopyInto(out *InstallPlanReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstallPlanReference. +func (in *InstallPlanReference) DeepCopy() *InstallPlanReference { + if in == nil { + return nil + } + out := new(InstallPlanReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InstallPlanSpec) DeepCopyInto(out *InstallPlanSpec) { + *out = *in + if in.ClusterServiceVersionNames != nil { + in, out := &in.ClusterServiceVersionNames, &out.ClusterServiceVersionNames + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstallPlanSpec. +func (in *InstallPlanSpec) DeepCopy() *InstallPlanSpec { + if in == nil { + return nil + } + out := new(InstallPlanSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InstallPlanStatus) DeepCopyInto(out *InstallPlanStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]InstallPlanCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.CatalogSources != nil { + in, out := &in.CatalogSources, &out.CatalogSources + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Plan != nil { + in, out := &in.Plan, &out.Plan + *out = make([]*Step, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(Step) + **out = **in + } + } + } + if in.BundleLookups != nil { + in, out := &in.BundleLookups, &out.BundleLookups + *out = make([]BundleLookup, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.AttenuatedServiceAccountRef != nil { + in, out := &in.AttenuatedServiceAccountRef, &out.AttenuatedServiceAccountRef + *out = new(v1.ObjectReference) + **out = **in + } + if in.StartTime != nil { + in, out := &in.StartTime, &out.StartTime + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstallPlanStatus. +func (in *InstallPlanStatus) DeepCopy() *InstallPlanStatus { + if in == nil { + return nil + } + out := new(InstallPlanStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Maintainer) DeepCopyInto(out *Maintainer) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Maintainer. +func (in *Maintainer) DeepCopy() *Maintainer { + if in == nil { + return nil + } + out := new(Maintainer) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamedInstallStrategy) DeepCopyInto(out *NamedInstallStrategy) { + *out = *in + in.StrategySpec.DeepCopyInto(&out.StrategySpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamedInstallStrategy. +func (in *NamedInstallStrategy) DeepCopy() *NamedInstallStrategy { + if in == nil { + return nil + } + out := new(NamedInstallStrategy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RegistryPoll) DeepCopyInto(out *RegistryPoll) { + *out = *in + if in.Interval != nil { + in, out := &in.Interval, &out.Interval + *out = new(metav1.Duration) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RegistryPoll. +func (in *RegistryPoll) DeepCopy() *RegistryPoll { + if in == nil { + return nil + } + out := new(RegistryPoll) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RegistryServiceStatus) DeepCopyInto(out *RegistryServiceStatus) { + *out = *in + in.CreatedAt.DeepCopyInto(&out.CreatedAt) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RegistryServiceStatus. +func (in *RegistryServiceStatus) DeepCopy() *RegistryServiceStatus { + if in == nil { + return nil + } + out := new(RegistryServiceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RelatedImage) DeepCopyInto(out *RelatedImage) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RelatedImage. +func (in *RelatedImage) DeepCopy() *RelatedImage { + if in == nil { + return nil + } + out := new(RelatedImage) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RequirementStatus) DeepCopyInto(out *RequirementStatus) { + *out = *in + if in.Dependents != nil { + in, out := &in.Dependents, &out.Dependents + *out = make([]DependentStatus, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequirementStatus. +func (in *RequirementStatus) DeepCopy() *RequirementStatus { + if in == nil { + return nil + } + out := new(RequirementStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceInstance) DeepCopyInto(out *ResourceInstance) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceInstance. +func (in *ResourceInstance) DeepCopy() *ResourceInstance { + if in == nil { + return nil + } + out := new(ResourceInstance) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceList) DeepCopyInto(out *ResourceList) { + *out = *in + if in.Instances != nil { + in, out := &in.Instances, &out.Instances + *out = make([]ResourceInstance, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceList. +func (in *ResourceList) DeepCopy() *ResourceList { + if in == nil { + return nil + } + out := new(ResourceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SpecDescriptor) DeepCopyInto(out *SpecDescriptor) { + *out = *in + if in.XDescriptors != nil { + in, out := &in.XDescriptors, &out.XDescriptors + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Value != nil { + in, out := &in.Value, &out.Value + *out = make(json.RawMessage, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpecDescriptor. +func (in *SpecDescriptor) DeepCopy() *SpecDescriptor { + if in == nil { + return nil + } + out := new(SpecDescriptor) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StatusDescriptor) DeepCopyInto(out *StatusDescriptor) { + *out = *in + if in.XDescriptors != nil { + in, out := &in.XDescriptors, &out.XDescriptors + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Value != nil { + in, out := &in.Value, &out.Value + *out = make(json.RawMessage, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatusDescriptor. +func (in *StatusDescriptor) DeepCopy() *StatusDescriptor { + if in == nil { + return nil + } + out := new(StatusDescriptor) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Step) DeepCopyInto(out *Step) { + *out = *in + out.Resource = in.Resource +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Step. +func (in *Step) DeepCopy() *Step { + if in == nil { + return nil + } + out := new(Step) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StepResource) DeepCopyInto(out *StepResource) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StepResource. +func (in *StepResource) DeepCopy() *StepResource { + if in == nil { + return nil + } + out := new(StepResource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StrategyDeploymentPermissions) DeepCopyInto(out *StrategyDeploymentPermissions) { + *out = *in + if in.Rules != nil { + in, out := &in.Rules, &out.Rules + *out = make([]rbacv1.PolicyRule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StrategyDeploymentPermissions. +func (in *StrategyDeploymentPermissions) DeepCopy() *StrategyDeploymentPermissions { + if in == nil { + return nil + } + out := new(StrategyDeploymentPermissions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StrategyDeploymentSpec) DeepCopyInto(out *StrategyDeploymentSpec) { + *out = *in + in.Spec.DeepCopyInto(&out.Spec) + if in.Label != nil { + in, out := &in.Label, &out.Label + *out = make(labels.Set, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StrategyDeploymentSpec. +func (in *StrategyDeploymentSpec) DeepCopy() *StrategyDeploymentSpec { + if in == nil { + return nil + } + out := new(StrategyDeploymentSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StrategyDetailsDeployment) DeepCopyInto(out *StrategyDetailsDeployment) { + *out = *in + if in.DeploymentSpecs != nil { + in, out := &in.DeploymentSpecs, &out.DeploymentSpecs + *out = make([]StrategyDeploymentSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Permissions != nil { + in, out := &in.Permissions, &out.Permissions + *out = make([]StrategyDeploymentPermissions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ClusterPermissions != nil { + in, out := &in.ClusterPermissions, &out.ClusterPermissions + *out = make([]StrategyDeploymentPermissions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StrategyDetailsDeployment. +func (in *StrategyDetailsDeployment) DeepCopy() *StrategyDetailsDeployment { + if in == nil { + return nil + } + out := new(StrategyDetailsDeployment) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Subscription) DeepCopyInto(out *Subscription) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.Spec != nil { + in, out := &in.Spec, &out.Spec + *out = new(SubscriptionSpec) + (*in).DeepCopyInto(*out) + } + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Subscription. +func (in *Subscription) DeepCopy() *Subscription { + if in == nil { + return nil + } + out := new(Subscription) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Subscription) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubscriptionCatalogHealth) DeepCopyInto(out *SubscriptionCatalogHealth) { + *out = *in + if in.CatalogSourceRef != nil { + in, out := &in.CatalogSourceRef, &out.CatalogSourceRef + *out = new(v1.ObjectReference) + **out = **in + } + if in.LastUpdated != nil { + in, out := &in.LastUpdated, &out.LastUpdated + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubscriptionCatalogHealth. +func (in *SubscriptionCatalogHealth) DeepCopy() *SubscriptionCatalogHealth { + if in == nil { + return nil + } + out := new(SubscriptionCatalogHealth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubscriptionCondition) DeepCopyInto(out *SubscriptionCondition) { + *out = *in + if in.LastHeartbeatTime != nil { + in, out := &in.LastHeartbeatTime, &out.LastHeartbeatTime + *out = (*in).DeepCopy() + } + if in.LastTransitionTime != nil { + in, out := &in.LastTransitionTime, &out.LastTransitionTime + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubscriptionCondition. +func (in *SubscriptionCondition) DeepCopy() *SubscriptionCondition { + if in == nil { + return nil + } + out := new(SubscriptionCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubscriptionConfig) DeepCopyInto(out *SubscriptionConfig) { + *out = *in + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + *out = new(metav1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = make([]v1.Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = new(v1.ResourceRequirements) + (*in).DeepCopyInto(*out) + } + if in.EnvFrom != nil { + in, out := &in.EnvFrom, &out.EnvFrom + *out = make([]v1.EnvFromSource, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Env != nil { + in, out := &in.Env, &out.Env + *out = make([]v1.EnvVar, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Volumes != nil { + in, out := &in.Volumes, &out.Volumes + *out = make([]v1.Volume, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.VolumeMounts != nil { + in, out := &in.VolumeMounts, &out.VolumeMounts + *out = make([]v1.VolumeMount, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Affinity != nil { + in, out := &in.Affinity, &out.Affinity + *out = new(v1.Affinity) + (*in).DeepCopyInto(*out) + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubscriptionConfig. +func (in *SubscriptionConfig) DeepCopy() *SubscriptionConfig { + if in == nil { + return nil + } + out := new(SubscriptionConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubscriptionList) DeepCopyInto(out *SubscriptionList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Subscription, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubscriptionList. +func (in *SubscriptionList) DeepCopy() *SubscriptionList { + if in == nil { + return nil + } + out := new(SubscriptionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SubscriptionList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubscriptionSpec) DeepCopyInto(out *SubscriptionSpec) { + *out = *in + if in.Config != nil { + in, out := &in.Config, &out.Config + *out = new(SubscriptionConfig) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubscriptionSpec. +func (in *SubscriptionSpec) DeepCopy() *SubscriptionSpec { + if in == nil { + return nil + } + out := new(SubscriptionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubscriptionStatus) DeepCopyInto(out *SubscriptionStatus) { + *out = *in + if in.Install != nil { + in, out := &in.Install, &out.Install + *out = new(InstallPlanReference) + **out = **in + } + if in.InstallPlanRef != nil { + in, out := &in.InstallPlanRef, &out.InstallPlanRef + *out = new(v1.ObjectReference) + **out = **in + } + if in.CatalogHealth != nil { + in, out := &in.CatalogHealth, &out.CatalogHealth + *out = make([]SubscriptionCatalogHealth, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]SubscriptionCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + in.LastUpdated.DeepCopyInto(&out.LastUpdated) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubscriptionStatus. +func (in *SubscriptionStatus) DeepCopy() *SubscriptionStatus { + if in == nil { + return nil + } + out := new(SubscriptionStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UpdateStrategy) DeepCopyInto(out *UpdateStrategy) { + *out = *in + if in.RegistryPoll != nil { + in, out := &in.RegistryPoll, &out.RegistryPoll + *out = new(RegistryPoll) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpdateStrategy. +func (in *UpdateStrategy) DeepCopy() *UpdateStrategy { + if in == nil { + return nil + } + out := new(UpdateStrategy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WebhookDescription) DeepCopyInto(out *WebhookDescription) { + *out = *in + if in.TargetPort != nil { + in, out := &in.TargetPort, &out.TargetPort + *out = new(intstr.IntOrString) + **out = **in + } + if in.Rules != nil { + in, out := &in.Rules, &out.Rules + *out = make([]admissionregistrationv1.RuleWithOperations, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.FailurePolicy != nil { + in, out := &in.FailurePolicy, &out.FailurePolicy + *out = new(admissionregistrationv1.FailurePolicyType) + **out = **in + } + if in.MatchPolicy != nil { + in, out := &in.MatchPolicy, &out.MatchPolicy + *out = new(admissionregistrationv1.MatchPolicyType) + **out = **in + } + if in.ObjectSelector != nil { + in, out := &in.ObjectSelector, &out.ObjectSelector + *out = new(metav1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.SideEffects != nil { + in, out := &in.SideEffects, &out.SideEffects + *out = new(admissionregistrationv1.SideEffectClass) + **out = **in + } + if in.TimeoutSeconds != nil { + in, out := &in.TimeoutSeconds, &out.TimeoutSeconds + *out = new(int32) + **out = **in + } + if in.AdmissionReviewVersions != nil { + in, out := &in.AdmissionReviewVersions, &out.AdmissionReviewVersions + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ReinvocationPolicy != nil { + in, out := &in.ReinvocationPolicy, &out.ReinvocationPolicy + *out = new(admissionregistrationv1.ReinvocationPolicyType) + **out = **in + } + if in.WebhookPath != nil { + in, out := &in.WebhookPath, &out.WebhookPath + *out = new(string) + **out = **in + } + if in.ConversionCRDs != nil { + in, out := &in.ConversionCRDs, &out.ConversionCRDs + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookDescription. +func (in *WebhookDescription) DeepCopy() *WebhookDescription { + if in == nil { + return nil + } + out := new(WebhookDescription) + in.DeepCopyInto(out) + return out +} diff --git a/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/zz_generated.model_name.go b/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/zz_generated.model_name.go new file mode 100644 index 000000000..555d6d7f0 --- /dev/null +++ b/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/zz_generated.model_name.go @@ -0,0 +1,306 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* + +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. +*/ + +// Code generated by openapi-gen-v0.0. DO NOT EDIT. + +package v1alpha1 + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in APIResourceReference) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.APIResourceReference" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in APIServiceDefinitions) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.APIServiceDefinitions" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in APIServiceDescription) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.APIServiceDescription" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ActionDescriptor) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.ActionDescriptor" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in AppLink) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.AppLink" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in BundleLookup) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.BundleLookup" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in BundleLookupCondition) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.BundleLookupCondition" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in CRDDescription) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.CRDDescription" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in CatalogSource) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.CatalogSource" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in CatalogSourceList) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.CatalogSourceList" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in CatalogSourceSpec) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.CatalogSourceSpec" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in CatalogSourceStatus) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.CatalogSourceStatus" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in CleanupSpec) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.CleanupSpec" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in CleanupStatus) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.CleanupStatus" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ClusterServiceVersion) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.ClusterServiceVersion" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ClusterServiceVersionCondition) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.ClusterServiceVersionCondition" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ClusterServiceVersionList) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.ClusterServiceVersionList" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ClusterServiceVersionSpec) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.ClusterServiceVersionSpec" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ClusterServiceVersionStatus) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.ClusterServiceVersionStatus" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ConfigMapResourceReference) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.ConfigMapResourceReference" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in CustomResourceDefinitions) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.CustomResourceDefinitions" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in DependentStatus) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.DependentStatus" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ExtractContentConfig) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.ExtractContentConfig" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in GRPCConnectionState) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.GRPCConnectionState" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in GrpcPodConfig) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.GrpcPodConfig" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Icon) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.Icon" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in InstallMode) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.InstallMode" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in InstallPlan) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.InstallPlan" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in InstallPlanCondition) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.InstallPlanCondition" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in InstallPlanList) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.InstallPlanList" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in InstallPlanReference) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.InstallPlanReference" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in InstallPlanSpec) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.InstallPlanSpec" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in InstallPlanStatus) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.InstallPlanStatus" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Maintainer) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.Maintainer" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in NamedInstallStrategy) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.NamedInstallStrategy" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in RegistryPoll) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.RegistryPoll" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in RegistryServiceStatus) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.RegistryServiceStatus" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in RelatedImage) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.RelatedImage" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in RequirementStatus) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.RequirementStatus" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ResourceInstance) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.ResourceInstance" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ResourceList) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.ResourceList" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in SpecDescriptor) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.SpecDescriptor" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in StatusDescriptor) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.StatusDescriptor" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Step) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.Step" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in StepResource) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.StepResource" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in StrategyDeploymentPermissions) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.StrategyDeploymentPermissions" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in StrategyDeploymentSpec) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.StrategyDeploymentSpec" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in StrategyDetailsDeployment) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.StrategyDetailsDeployment" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Subscription) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.Subscription" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in SubscriptionCatalogHealth) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.SubscriptionCatalogHealth" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in SubscriptionCondition) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.SubscriptionCondition" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in SubscriptionConfig) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.SubscriptionConfig" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in SubscriptionList) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.SubscriptionList" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in SubscriptionSpec) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.SubscriptionSpec" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in SubscriptionStatus) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.SubscriptionStatus" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in UpdateStrategy) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.UpdateStrategy" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in WebhookDescription) OpenAPIModelName() string { + return "com.github.operator-framework.api.pkg.operators.v1alpha1.WebhookDescription" +} diff --git a/vendor/github.com/sirupsen/logrus/.golangci.yml b/vendor/github.com/sirupsen/logrus/.golangci.yml index 65dc28503..792db3618 100644 --- a/vendor/github.com/sirupsen/logrus/.golangci.yml +++ b/vendor/github.com/sirupsen/logrus/.golangci.yml @@ -1,40 +1,67 @@ +version: "2" run: - # do not run on test files yet tests: false - -# all available settings of specific linters -linters-settings: - errcheck: - # report about not checking of errors in type assetions: `a := b.(MyStruct)`; - # default is false: such cases aren't reported by default. - check-type-assertions: false - - # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; - # default is false: such cases aren't reported by default. - check-blank: false - - lll: - line-length: 100 - tab-width: 4 - - prealloc: - simple: false - range-loops: false - for-loops: false - - whitespace: - multi-if: false # Enforces newlines (or comments) after every multi-line if statement - multi-func: false # Enforces newlines (or comments) after every multi-line function signature - linters: enable: - - megacheck - - govet + - asasalint + - asciicheck + - bidichk + - bodyclose + - contextcheck + - durationcheck + - errchkjson + - errorlint + - exhaustive + - gocheckcompilerdirectives + - gochecksumtype + - gosec + - gosmopolitan + - loggercheck + - makezero + - musttag + - nilerr + - nilnesserr + - noctx + - protogetter + - reassign + - recvcheck + - rowserrcheck + - spancheck + - sqlclosecheck + - testifylint + - unparam + - zerologlint disable: - - maligned - prealloc - disable-all: false - presets: - - bugs - - unused - fast: false + settings: + errcheck: + check-type-assertions: false + check-blank: false + lll: + line-length: 100 + tab-width: 4 + prealloc: + simple: false + range-loops: false + for-loops: false + whitespace: + multi-if: false + multi-func: false + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/vendor/github.com/sirupsen/logrus/CHANGELOG.md b/vendor/github.com/sirupsen/logrus/CHANGELOG.md index 7567f6128..098608ff4 100644 --- a/vendor/github.com/sirupsen/logrus/CHANGELOG.md +++ b/vendor/github.com/sirupsen/logrus/CHANGELOG.md @@ -37,7 +37,7 @@ Features: # 1.6.0 Fixes: * end of line cleanup - * revert the entry concurrency bug fix whic leads to deadlock under some circumstances + * revert the entry concurrency bug fix which leads to deadlock under some circumstances * update dependency on go-windows-terminal-sequences to fix a crash with go 1.14 Features: @@ -129,7 +129,7 @@ This new release introduces: which is mostly useful for logger wrapper * a fix reverting the immutability of the entry given as parameter to the hooks a new configuration field of the json formatter in order to put all the fields - in a nested dictionnary + in a nested dictionary * a new SetOutput method in the Logger * a new configuration of the textformatter to configure the name of the default keys * a new configuration of the text formatter to disable the level truncation diff --git a/vendor/github.com/sirupsen/logrus/README.md b/vendor/github.com/sirupsen/logrus/README.md index d1d4a85fd..cc5dab7eb 100644 --- a/vendor/github.com/sirupsen/logrus/README.md +++ b/vendor/github.com/sirupsen/logrus/README.md @@ -1,4 +1,4 @@ -# Logrus :walrus: [![Build Status](https://github.com/sirupsen/logrus/workflows/CI/badge.svg)](https://github.com/sirupsen/logrus/actions?query=workflow%3ACI) [![Build Status](https://travis-ci.org/sirupsen/logrus.svg?branch=master)](https://travis-ci.org/sirupsen/logrus) [![Go Reference](https://pkg.go.dev/badge/github.com/sirupsen/logrus.svg)](https://pkg.go.dev/github.com/sirupsen/logrus) +# Logrus :walrus: [![Build Status](https://github.com/sirupsen/logrus/workflows/CI/badge.svg)](https://github.com/sirupsen/logrus/actions?query=workflow%3ACI) [![Go Reference](https://pkg.go.dev/badge/github.com/sirupsen/logrus.svg)](https://pkg.go.dev/github.com/sirupsen/logrus) Logrus is a structured logger for Go (golang), completely API compatible with the standard library logger. @@ -40,7 +40,7 @@ plain text): ![Colored](http://i.imgur.com/PY7qMwd.png) -With `log.SetFormatter(&log.JSONFormatter{})`, for easy parsing by logstash +With `logrus.SetFormatter(&logrus.JSONFormatter{})`, for easy parsing by logstash or Splunk: ```text @@ -60,9 +60,9 @@ ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"} "time":"2014-03-10 19:57:38.562543128 -0400 EDT"} ``` -With the default `log.SetFormatter(&log.TextFormatter{})` when a TTY is not +With the default `logrus.SetFormatter(&logrus.TextFormatter{})` when a TTY is not attached, the output is compatible with the -[logfmt](http://godoc.org/github.com/kr/logfmt) format: +[logfmt](https://pkg.go.dev/github.com/kr/logfmt) format: ```text time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8 @@ -75,17 +75,18 @@ time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x20822 To ensure this behaviour even if a TTY is attached, set your formatter as follows: ```go - log.SetFormatter(&log.TextFormatter{ - DisableColors: true, - FullTimestamp: true, - }) +logrus.SetFormatter(&logrus.TextFormatter{ + DisableColors: true, + FullTimestamp: true, +}) ``` #### Logging Method Name If you wish to add the calling method as a field, instruct the logger via: + ```go -log.SetReportCaller(true) +logrus.SetReportCaller(true) ``` This adds the caller as 'method' like so: @@ -100,11 +101,11 @@ time="2015-03-26T01:27:38-04:00" level=fatal method=github.com/sirupsen/arcticcr Note that this does add measurable overhead - the cost will depend on the version of Go, but is between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your environment via benchmarks: -``` + +```bash go test -bench=.*CallerTracing ``` - #### Case-sensitivity The organization's name was changed to lower-case--and this will not be changed @@ -118,12 +119,10 @@ The simplest way to use Logrus is simply the package-level exported logger: ```go package main -import ( - log "github.com/sirupsen/logrus" -) +import "github.com/sirupsen/logrus" func main() { - log.WithFields(log.Fields{ + logrus.WithFields(logrus.Fields{ "animal": "walrus", }).Info("A walrus appears") } @@ -139,6 +138,7 @@ package main import ( "os" + log "github.com/sirupsen/logrus" ) @@ -190,26 +190,27 @@ package main import ( "os" + "github.com/sirupsen/logrus" ) // Create a new instance of the logger. You can have any number of instances. -var log = logrus.New() +var logger = logrus.New() func main() { // The API for setting attributes is a little different than the package level - // exported logger. See Godoc. - log.Out = os.Stdout + // exported logger. See Godoc. + logger.Out = os.Stdout // You could set this to any `io.Writer` such as a file // file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) // if err == nil { - // log.Out = file + // logger.Out = file // } else { - // log.Info("Failed to log to file, using default stderr") + // logger.Info("Failed to log to file, using default stderr") // } - log.WithFields(logrus.Fields{ + logger.WithFields(logrus.Fields{ "animal": "walrus", "size": 10, }).Info("A group of walrus emerges from the ocean") @@ -219,12 +220,12 @@ func main() { #### Fields Logrus encourages careful, structured logging through logging fields instead of -long, unparseable error messages. For example, instead of: `log.Fatalf("Failed +long, unparseable error messages. For example, instead of: `logrus.Fatalf("Failed to send event %s to topic %s with key %d")`, you should log the much more discoverable: ```go -log.WithFields(log.Fields{ +logrus.WithFields(logrus.Fields{ "event": event, "topic": topic, "key": key, @@ -245,12 +246,12 @@ seen as a hint you should add a field, however, you can still use the Often it's helpful to have fields _always_ attached to log statements in an application or parts of one. For example, you may want to always log the `request_id` and `user_ip` in the context of a request. Instead of writing -`log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})` on +`logger.WithFields(logrus.Fields{"request_id": request_id, "user_ip": user_ip})` on every line, you can create a `logrus.Entry` to pass around instead: ```go -requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip}) -requestLogger.Info("something happened on that request") # will log request_id and user_ip +requestLogger := logger.WithFields(logrus.Fields{"request_id": request_id, "user_ip": user_ip}) +requestLogger.Info("something happened on that request") // will log request_id and user_ip requestLogger.Warn("something not great happened") ``` @@ -264,28 +265,31 @@ Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in `init`: ```go +package main + import ( - log "github.com/sirupsen/logrus" - "gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake" - logrus_syslog "github.com/sirupsen/logrus/hooks/syslog" "log/syslog" + + "github.com/sirupsen/logrus" + airbrake "gopkg.in/gemnasium/logrus-airbrake-hook.v2" + logrus_syslog "github.com/sirupsen/logrus/hooks/syslog" ) func init() { // Use the Airbrake hook to report errors that have Error severity or above to // an exception tracker. You can create custom hooks, see the Hooks section. - log.AddHook(airbrake.NewHook(123, "xyz", "production")) + logrus.AddHook(airbrake.NewHook(123, "xyz", "production")) hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") if err != nil { - log.Error("Unable to connect to local syslog daemon") + logrus.Error("Unable to connect to local syslog daemon") } else { - log.AddHook(hook) + logrus.AddHook(hook) } } ``` -Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md). +Note: Syslog hooks also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md). A list of currently known service hooks can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks) @@ -295,15 +299,15 @@ A list of currently known service hooks can be found in this wiki [page](https:/ Logrus has seven logging levels: Trace, Debug, Info, Warning, Error, Fatal and Panic. ```go -log.Trace("Something very low level.") -log.Debug("Useful debugging information.") -log.Info("Something noteworthy happened!") -log.Warn("You should probably take a look at this.") -log.Error("Something failed but I'm not quitting.") +logrus.Trace("Something very low level.") +logrus.Debug("Useful debugging information.") +logrus.Info("Something noteworthy happened!") +logrus.Warn("You should probably take a look at this.") +logrus.Error("Something failed but I'm not quitting.") // Calls os.Exit(1) after logging -log.Fatal("Bye.") +logrus.Fatal("Bye.") // Calls panic() after logging -log.Panic("I'm bailing.") +logrus.Panic("I'm bailing.") ``` You can set the logging level on a `Logger`, then it will only log entries with @@ -311,13 +315,13 @@ that severity or anything above it: ```go // Will log anything that is info or above (warn, error, fatal, panic). Default. -log.SetLevel(log.InfoLevel) +logrus.SetLevel(logrus.InfoLevel) ``` -It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose +It may be useful to set `logrus.Level = logrus.DebugLevel` in a debug or verbose environment if your application has that. -Note: If you want different log levels for global (`log.SetLevel(...)`) and syslog logging, please check the [syslog hook README](hooks/syslog/README.md#different-log-levels-for-local-and-remote-logging). +Note: If you want different log levels for global (`logrus.SetLevel(...)`) and syslog logging, please check the [syslog hook README](hooks/syslog/README.md#different-log-levels-for-local-and-remote-logging). #### Entries @@ -340,17 +344,17 @@ could do: ```go import ( - log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" ) func init() { // do something here to set environment depending on an environment variable // or command-line flag if Environment == "production" { - log.SetFormatter(&log.JSONFormatter{}) + logrus.SetFormatter(&logrus.JSONFormatter{}) } else { // The TextFormatter is default, you don't actually have to do this. - log.SetFormatter(&log.TextFormatter{}) + logrus.SetFormatter(&logrus.TextFormatter{}) } } ``` @@ -372,11 +376,11 @@ The built-in logging formatters are: * When colors are enabled, levels are truncated to 4 characters by default. To disable truncation set the `DisableLevelTruncation` field to `true`. * When outputting to a TTY, it's often helpful to visually scan down a column where all the levels are the same width. Setting the `PadLevelText` field to `true` enables this behavior, by adding padding to the level text. - * All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#TextFormatter). + * All options are listed in the [generated docs](https://pkg.go.dev/github.com/sirupsen/logrus#TextFormatter). * `logrus.JSONFormatter`. Logs fields as JSON. - * All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#JSONFormatter). + * All options are listed in the [generated docs](https://pkg.go.dev/github.com/sirupsen/logrus#JSONFormatter). -Third party logging formatters: +Third-party logging formatters: * [`FluentdFormatter`](https://github.com/joonix/log). Formats entries that can be parsed by Kubernetes and Google Container Engine. * [`GELF`](https://github.com/fabienm/go-logrus-formatters). Formats entries so they comply to Graylog's [GELF 1.1 specification](http://docs.graylog.org/en/2.4/pages/gelf.html). @@ -384,7 +388,7 @@ Third party logging formatters: * [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout. * [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the Power of Zalgo. * [`nested-logrus-formatter`](https://github.com/antonfisher/nested-logrus-formatter). Converts logrus fields to a nested structure. -* [`powerful-logrus-formatter`](https://github.com/zput/zxcTool). get fileName, log's line number and the latest function's name when print log; Sava log to files. +* [`powerful-logrus-formatter`](https://github.com/zput/zxcTool). get fileName, log's line number and the latest function's name when print log; Save log to files. * [`caption-json-formatter`](https://github.com/nolleh/caption_json_formatter). logrus's message json formatter with human-readable caption added. You can define your formatter by implementing the `Formatter` interface, @@ -393,10 +397,9 @@ requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a default ones (see Entries section above): ```go -type MyJSONFormatter struct { -} +type MyJSONFormatter struct{} -log.SetFormatter(new(MyJSONFormatter)) +logrus.SetFormatter(new(MyJSONFormatter)) func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) { // Note this doesn't include Time, Level and Message which are available on @@ -455,17 +458,18 @@ entries. It should not be a feature of the application-level logger. #### Testing -Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides: +Logrus has a built-in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides: * decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just adds the `test` hook * a test logger (`test.NewNullLogger`) that just records log messages (and does not output any): ```go import( + "testing" + "github.com/sirupsen/logrus" "github.com/sirupsen/logrus/hooks/test" "github.com/stretchr/testify/assert" - "testing" ) func TestSomething(t*testing.T){ @@ -486,15 +490,15 @@ func TestSomething(t*testing.T){ Logrus can register one or more functions that will be called when any `fatal` level message is logged. The registered handlers will be executed before logrus performs an `os.Exit(1)`. This behavior may be helpful if callers need -to gracefully shutdown. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted. +to gracefully shut down. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted. -``` -... +```go +// ... handler := func() { - // gracefully shutdown something... + // gracefully shut down something... } logrus.RegisterExitHandler(handler) -... +// ... ``` #### Thread safety @@ -502,7 +506,7 @@ logrus.RegisterExitHandler(handler) By default, Logger is protected by a mutex for concurrent writes. The mutex is held when calling hooks and writing logs. If you are sure such locking is not needed, you can call logger.SetNoLock() to disable the locking. -Situation when locking is not needed includes: +Situations when locking is not needed include: * You have no hooks registered, or hooks calling is already thread-safe. diff --git a/vendor/github.com/sirupsen/logrus/appveyor.yml b/vendor/github.com/sirupsen/logrus/appveyor.yml index df9d65c3a..e90f09ea6 100644 --- a/vendor/github.com/sirupsen/logrus/appveyor.yml +++ b/vendor/github.com/sirupsen/logrus/appveyor.yml @@ -1,14 +1,12 @@ -version: "{build}" +# Minimal stub to satisfy AppVeyor CI +version: 1.0.{build} platform: x64 -clone_folder: c:\gopath\src\github.com\sirupsen\logrus -environment: - GOPATH: c:\gopath +shallow_clone: true + branches: only: - master -install: - - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% - - go version + - main + build_script: - - go get -t - - go test + - echo "No-op build to satisfy AppVeyor CI" diff --git a/vendor/github.com/sirupsen/logrus/entry.go b/vendor/github.com/sirupsen/logrus/entry.go index 71cdbbc35..71d796d0b 100644 --- a/vendor/github.com/sirupsen/logrus/entry.go +++ b/vendor/github.com/sirupsen/logrus/entry.go @@ -34,13 +34,15 @@ func init() { minimumCallerDepth = 1 } -// Defines the key when adding errors using WithError. +// ErrorKey defines the key when adding errors using [WithError], [Logger.WithError]. var ErrorKey = "error" -// An entry is the final or intermediate Logrus logging entry. It contains all +// Entry is the final or intermediate Logrus logging entry. It contains all // the fields passed with WithField{,s}. It's finally logged when Trace, Debug, // Info, Warn, Error, Fatal or Panic is called on it. These objects can be // reused and passed around as much as you wish to avoid field duplication. +// +//nolint:recvcheck // the methods of "Entry" use pointer receiver and non-pointer receiver. type Entry struct { Logger *Logger @@ -86,12 +88,12 @@ func (entry *Entry) Dup() *Entry { return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, Context: entry.Context, err: entry.err} } -// Returns the bytes representation of this entry from the formatter. +// Bytes returns the bytes representation of this entry from the formatter. func (entry *Entry) Bytes() ([]byte, error) { return entry.Logger.Formatter.Format(entry) } -// Returns the string representation from the reader and ultimately the +// String returns the string representation from the reader and ultimately the // formatter. func (entry *Entry) String() (string, error) { serialized, err := entry.Bytes() @@ -102,12 +104,13 @@ func (entry *Entry) String() (string, error) { return str, nil } -// Add an error as single field (using the key defined in ErrorKey) to the Entry. +// WithError adds an error as single field (using the key defined in [ErrorKey]) +// to the Entry. func (entry *Entry) WithError(err error) *Entry { return entry.WithField(ErrorKey, err) } -// Add a context to the Entry. +// WithContext adds a context to the Entry. func (entry *Entry) WithContext(ctx context.Context) *Entry { dataCopy := make(Fields, len(entry.Data)) for k, v := range entry.Data { @@ -116,12 +119,12 @@ func (entry *Entry) WithContext(ctx context.Context) *Entry { return &Entry{Logger: entry.Logger, Data: dataCopy, Time: entry.Time, err: entry.err, Context: ctx} } -// Add a single field to the Entry. +// WithField adds a single field to the Entry. func (entry *Entry) WithField(key string, value interface{}) *Entry { return entry.WithFields(Fields{key: value}) } -// Add a map of fields to the Entry. +// WithFields adds a map of fields to the Entry. func (entry *Entry) WithFields(fields Fields) *Entry { data := make(Fields, len(entry.Data)+len(fields)) for k, v := range entry.Data { @@ -150,7 +153,7 @@ func (entry *Entry) WithFields(fields Fields) *Entry { return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: fieldErr, Context: entry.Context} } -// Overrides the time of the Entry. +// WithTime overrides the time of the Entry. func (entry *Entry) WithTime(t time.Time) *Entry { dataCopy := make(Fields, len(entry.Data)) for k, v := range entry.Data { @@ -204,7 +207,7 @@ func getCaller() *runtime.Frame { // If the caller isn't part of this package, we're done if pkg != logrusPackage { - return &f //nolint:scopelint + return &f } } @@ -432,7 +435,7 @@ func (entry *Entry) Panicln(args ...interface{}) { entry.Logln(PanicLevel, args...) } -// Sprintlnn => Sprint no newline. This is to get the behavior of how +// sprintlnn => Sprint no newline. This is to get the behavior of how // fmt.Sprintln where spaces are always added between operands, regardless of // their type. Instead of vendoring the Sprintln implementation to spare a // string allocation, we do the simplest thing. diff --git a/vendor/github.com/sirupsen/logrus/hooks.go b/vendor/github.com/sirupsen/logrus/hooks.go index 3f151cdc3..9ab978a45 100644 --- a/vendor/github.com/sirupsen/logrus/hooks.go +++ b/vendor/github.com/sirupsen/logrus/hooks.go @@ -1,16 +1,16 @@ package logrus -// A hook to be fired when logging on the logging levels returned from -// `Levels()` on your implementation of the interface. Note that this is not +// Hook describes hooks to be fired when logging on the logging levels returned from +// [Hook.Levels] on your implementation of the interface. Note that this is not // fired in a goroutine or a channel with workers, you should handle such -// functionality yourself if your call is non-blocking and you don't wish for +// functionality yourself if your call is non-blocking, and you don't wish for // the logging calls for levels returned from `Levels()` to block. type Hook interface { Levels() []Level Fire(*Entry) error } -// Internal type for storing the hooks on a logger instance. +// LevelHooks is an internal type for storing the hooks on a logger instance. type LevelHooks map[Level][]Hook // Add a hook to an instance of logger. This is called with diff --git a/vendor/github.com/sirupsen/logrus/logger.go b/vendor/github.com/sirupsen/logrus/logger.go index 5ff0aef6d..f5b8c439e 100644 --- a/vendor/github.com/sirupsen/logrus/logger.go +++ b/vendor/github.com/sirupsen/logrus/logger.go @@ -72,16 +72,16 @@ func (mw *MutexWrap) Disable() { mw.disabled = true } -// Creates a new logger. Configuration should be set by changing `Formatter`, -// `Out` and `Hooks` directly on the default logger instance. You can also just +// New Creates a new logger. Configuration should be set by changing [Formatter], +// Out and Hooks directly on the default Logger instance. You can also just // instantiate your own: // -// var log = &logrus.Logger{ -// Out: os.Stderr, -// Formatter: new(logrus.TextFormatter), -// Hooks: make(logrus.LevelHooks), -// Level: logrus.DebugLevel, -// } +// var log = &logrus.Logger{ +// Out: os.Stderr, +// Formatter: new(logrus.TextFormatter), +// Hooks: make(logrus.LevelHooks), +// Level: logrus.DebugLevel, +// } // // It's recommended to make this a global instance called `log`. func New() *Logger { @@ -118,30 +118,30 @@ func (logger *Logger) WithField(key string, value interface{}) *Entry { return entry.WithField(key, value) } -// Adds a struct of fields to the log entry. All it does is call `WithField` for -// each `Field`. +// WithFields adds a struct of fields to the log entry. It calls [Entry.WithField] +// for each Field. func (logger *Logger) WithFields(fields Fields) *Entry { entry := logger.newEntry() defer logger.releaseEntry(entry) return entry.WithFields(fields) } -// Add an error as single field to the log entry. All it does is call -// `WithError` for the given `error`. +// WithError adds an error as single field to the log entry. It calls +// [Entry.WithError] for the given error. func (logger *Logger) WithError(err error) *Entry { entry := logger.newEntry() defer logger.releaseEntry(entry) return entry.WithError(err) } -// Add a context to the log entry. +// WithContext add a context to the log entry. func (logger *Logger) WithContext(ctx context.Context) *Entry { entry := logger.newEntry() defer logger.releaseEntry(entry) return entry.WithContext(ctx) } -// Overrides the time of the log entry. +// WithTime overrides the time of the log entry. func (logger *Logger) WithTime(t time.Time) *Entry { entry := logger.newEntry() defer logger.releaseEntry(entry) @@ -347,9 +347,9 @@ func (logger *Logger) Exit(code int) { logger.ExitFunc(code) } -//When file is opened with appending mode, it's safe to -//write concurrently to a file (within 4k message on Linux). -//In these cases user can choose to disable the lock. +// SetNoLock disables the lock for situations where a file is opened with +// appending mode, and safe for concurrent writes to the file (within 4k +// message on Linux). In these cases user can choose to disable the lock. func (logger *Logger) SetNoLock() { logger.mu.Disable() } diff --git a/vendor/github.com/sirupsen/logrus/logrus.go b/vendor/github.com/sirupsen/logrus/logrus.go index 2f16224cb..37fc4fef8 100644 --- a/vendor/github.com/sirupsen/logrus/logrus.go +++ b/vendor/github.com/sirupsen/logrus/logrus.go @@ -6,13 +6,15 @@ import ( "strings" ) -// Fields type, used to pass to `WithFields`. +// Fields type, used to pass to [WithFields]. type Fields map[string]interface{} // Level type +// +//nolint:recvcheck // the methods of "Entry" use pointer receiver and non-pointer receiver. type Level uint32 -// Convert the Level to a string. E.g. PanicLevel becomes "panic". +// Convert the Level to a string. E.g. [PanicLevel] becomes "panic". func (level Level) String() string { if b, err := level.MarshalText(); err == nil { return string(b) @@ -77,7 +79,7 @@ func (level Level) MarshalText() ([]byte, error) { return nil, fmt.Errorf("not a valid logrus level %d", level) } -// A constant exposing all logging levels +// AllLevels exposing all logging levels. var AllLevels = []Level{ PanicLevel, FatalLevel, @@ -119,8 +121,8 @@ var ( ) // StdLogger is what your logrus-enabled library should take, that way -// it'll accept a stdlib logger and a logrus logger. There's no standard -// interface, this is the closest we get, unfortunately. +// it'll accept a stdlib logger ([log.Logger]) and a logrus logger. +// There's no standard interface, so this is the closest we get, unfortunately. type StdLogger interface { Print(...interface{}) Printf(string, ...interface{}) @@ -135,7 +137,8 @@ type StdLogger interface { Panicln(...interface{}) } -// The FieldLogger interface generalizes the Entry and Logger types +// FieldLogger extends the [StdLogger] interface, generalizing +// the [Entry] and [Logger] types. type FieldLogger interface { WithField(key string, value interface{}) *Entry WithFields(fields Fields) *Entry @@ -176,8 +179,9 @@ type FieldLogger interface { // IsPanicEnabled() bool } -// Ext1FieldLogger (the first extension to FieldLogger) is superfluous, it is -// here for consistancy. Do not use. Use Logger or Entry instead. +// Ext1FieldLogger (the first extension to [FieldLogger]) is superfluous, it is +// here for consistency. Do not use. Use [FieldLogger], [Logger] or [Entry] +// instead. type Ext1FieldLogger interface { FieldLogger Tracef(format string, args ...interface{}) diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_bsd.go b/vendor/github.com/sirupsen/logrus/terminal_check_bsd.go index 499789984..69956b425 100644 --- a/vendor/github.com/sirupsen/logrus/terminal_check_bsd.go +++ b/vendor/github.com/sirupsen/logrus/terminal_check_bsd.go @@ -1,4 +1,4 @@ -// +build darwin dragonfly freebsd netbsd openbsd +// +build darwin dragonfly freebsd netbsd openbsd hurd // +build !js package logrus diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_unix.go b/vendor/github.com/sirupsen/logrus/terminal_check_unix.go index 04748b851..c9aed267a 100644 --- a/vendor/github.com/sirupsen/logrus/terminal_check_unix.go +++ b/vendor/github.com/sirupsen/logrus/terminal_check_unix.go @@ -1,5 +1,7 @@ +//go:build (linux || aix || zos) && !js && !wasi // +build linux aix zos // +build !js +// +build !wasi package logrus diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_wasi.go b/vendor/github.com/sirupsen/logrus/terminal_check_wasi.go new file mode 100644 index 000000000..2822b212f --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_wasi.go @@ -0,0 +1,8 @@ +//go:build wasi +// +build wasi + +package logrus + +func isTerminal(fd int) bool { + return false +} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_wasip1.go b/vendor/github.com/sirupsen/logrus/terminal_check_wasip1.go new file mode 100644 index 000000000..108a6be12 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_wasip1.go @@ -0,0 +1,8 @@ +//go:build wasip1 +// +build wasip1 + +package logrus + +func isTerminal(fd int) bool { + return false +} diff --git a/vendor/github.com/sirupsen/logrus/text_formatter.go b/vendor/github.com/sirupsen/logrus/text_formatter.go index be2c6efe5..6dfeb18b1 100644 --- a/vendor/github.com/sirupsen/logrus/text_formatter.go +++ b/vendor/github.com/sirupsen/logrus/text_formatter.go @@ -306,6 +306,7 @@ func (f *TextFormatter) needsQuoting(text string) bool { return false } for _, ch := range text { + //nolint:staticcheck // QF1001: could apply De Morgan's law if !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || @@ -334,6 +335,6 @@ func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) { if !f.needsQuoting(stringVal) { b.WriteString(stringVal) } else { - b.WriteString(fmt.Sprintf("%q", stringVal)) + fmt.Fprintf(b, "%q", stringVal) } } diff --git a/vendor/github.com/stoewer/go-strcase/.gitignore b/vendor/github.com/stoewer/go-strcase/.gitignore deleted file mode 100644 index db5247b94..000000000 --- a/vendor/github.com/stoewer/go-strcase/.gitignore +++ /dev/null @@ -1,17 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -vendor -doc - -# Temporary files -*~ -*.swp - -# Editor and IDE config -.idea -*.iml -.vscode diff --git a/vendor/github.com/stoewer/go-strcase/.golangci.yml b/vendor/github.com/stoewer/go-strcase/.golangci.yml deleted file mode 100644 index 7f98d55c4..000000000 --- a/vendor/github.com/stoewer/go-strcase/.golangci.yml +++ /dev/null @@ -1,26 +0,0 @@ -run: - deadline: 10m - -linters: - enable: - - dupl - - goconst - - gocyclo - - godox - - gosec - - interfacer - - lll - - maligned - - misspell - - prealloc - - stylecheck - - unconvert - - unparam - - errcheck - - golint - - gofmt - disable: [] - fast: false - -issues: - exclude-use-default: false diff --git a/vendor/github.com/stoewer/go-strcase/LICENSE b/vendor/github.com/stoewer/go-strcase/LICENSE deleted file mode 100644 index a105a3819..000000000 --- a/vendor/github.com/stoewer/go-strcase/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2017, Adrian Stoewer - -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. diff --git a/vendor/github.com/stoewer/go-strcase/README.md b/vendor/github.com/stoewer/go-strcase/README.md deleted file mode 100644 index 84a640e71..000000000 --- a/vendor/github.com/stoewer/go-strcase/README.md +++ /dev/null @@ -1,50 +0,0 @@ -[![GH Actions](https://github.com/stoewer/go-strcase/actions/workflows/lint-test.yml/badge.svg?branch=master)](https://github.com/stoewer/go-strcase/actions) -[![codecov](https://codecov.io/github/stoewer/go-strcase/branch/master/graph/badge.svg?token=c0UokYnop5)](https://codecov.io/github/stoewer/go-strcase) -[![GoDoc](https://godoc.org/github.com/stoewer/go-strcase?status.svg)](https://pkg.go.dev/github.com/stoewer/go-strcase) ---- - -Go strcase -========== - -The package `strcase` converts between different kinds of naming formats such as camel case -(`CamelCase`), snake case (`snake_case`) or kebab case (`kebab-case`). -The package is designed to work only with strings consisting of standard ASCII letters. -Unicode is currently not supported. - -Versioning and stability ------------------------- - -Although the master branch is supposed to remain always backward compatible, the repository -contains version tags in order to support vendoring tools. -The tag names follow semantic versioning conventions and have the following format `v1.0.0`. -This package supports Go modules introduced with version 1.11. - -Example -------- - -```go -import "github.com/stoewer/go-strcase" - -var snake = strcase.SnakeCase("CamelCase") -``` - -Dependencies ------------- - -### Build dependencies - -* none - -### Test dependencies - -* `github.com/stretchr/testify` - -Run linters and unit tests --------------------------- - -To run the static code analysis, linters and tests use the following commands: - -``` -golangci-lint run --config .golangci.yml ./... -go test ./... -``` diff --git a/vendor/github.com/stoewer/go-strcase/camel.go b/vendor/github.com/stoewer/go-strcase/camel.go deleted file mode 100644 index ff9e66e0c..000000000 --- a/vendor/github.com/stoewer/go-strcase/camel.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2017, A. Stoewer -// All rights reserved. - -package strcase - -import ( - "strings" -) - -// UpperCamelCase converts a string into camel case starting with a upper case letter. -func UpperCamelCase(s string) string { - return camelCase(s, true) -} - -// LowerCamelCase converts a string into camel case starting with a lower case letter. -func LowerCamelCase(s string) string { - return camelCase(s, false) -} - -func camelCase(s string, upper bool) string { - s = strings.TrimSpace(s) - buffer := make([]rune, 0, len(s)) - - stringIter(s, func(prev, curr, next rune) { - if !isDelimiter(curr) { - if isDelimiter(prev) || (upper && prev == 0) { - buffer = append(buffer, toUpper(curr)) - } else if isLower(prev) { - buffer = append(buffer, curr) - } else if isUpper(prev) && isUpper(curr) && isLower(next) { - // Assume a case like "R" for "XRequestId" - buffer = append(buffer, curr) - } else { - buffer = append(buffer, toLower(curr)) - } - } - }) - - return string(buffer) -} diff --git a/vendor/github.com/stoewer/go-strcase/doc.go b/vendor/github.com/stoewer/go-strcase/doc.go deleted file mode 100644 index 3e441ca3e..000000000 --- a/vendor/github.com/stoewer/go-strcase/doc.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2017, A. Stoewer -// All rights reserved. - -// Package strcase converts between different kinds of naming formats such as camel case -// (CamelCase), snake case (snake_case) or kebab case (kebab-case). The package is designed -// to work only with strings consisting of standard ASCII letters. Unicode is currently not -// supported. -package strcase diff --git a/vendor/github.com/stoewer/go-strcase/helper.go b/vendor/github.com/stoewer/go-strcase/helper.go deleted file mode 100644 index ecad58914..000000000 --- a/vendor/github.com/stoewer/go-strcase/helper.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2017, A. Stoewer -// All rights reserved. - -package strcase - -// isLower checks if a character is lower case. More precisely it evaluates if it is -// in the range of ASCII character 'a' to 'z'. -func isLower(ch rune) bool { - return ch >= 'a' && ch <= 'z' -} - -// toLower converts a character in the range of ASCII characters 'A' to 'Z' to its lower -// case counterpart. Other characters remain the same. -func toLower(ch rune) rune { - if ch >= 'A' && ch <= 'Z' { - return ch + 32 - } - return ch -} - -// isLower checks if a character is upper case. More precisely it evaluates if it is -// in the range of ASCII characters 'A' to 'Z'. -func isUpper(ch rune) bool { - return ch >= 'A' && ch <= 'Z' -} - -// toLower converts a character in the range of ASCII characters 'a' to 'z' to its lower -// case counterpart. Other characters remain the same. -func toUpper(ch rune) rune { - if ch >= 'a' && ch <= 'z' { - return ch - 32 - } - return ch -} - -// isSpace checks if a character is some kind of whitespace. -func isSpace(ch rune) bool { - return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' -} - -// isDelimiter checks if a character is some kind of whitespace or '_' or '-'. -func isDelimiter(ch rune) bool { - return ch == '-' || ch == '_' || isSpace(ch) -} - -// iterFunc is a callback that is called fro a specific position in a string. Its arguments are the -// rune at the respective string position as well as the previous and the next rune. If curr is at the -// first position of the string prev is zero. If curr is at the end of the string next is zero. -type iterFunc func(prev, curr, next rune) - -// stringIter iterates over a string, invoking the callback for every single rune in the string. -func stringIter(s string, callback iterFunc) { - var prev rune - var curr rune - for _, next := range s { - if curr == 0 { - prev = curr - curr = next - continue - } - - callback(prev, curr, next) - - prev = curr - curr = next - } - - if len(s) > 0 { - callback(prev, curr, 0) - } -} diff --git a/vendor/github.com/stoewer/go-strcase/kebab.go b/vendor/github.com/stoewer/go-strcase/kebab.go deleted file mode 100644 index e9a648757..000000000 --- a/vendor/github.com/stoewer/go-strcase/kebab.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) 2017, A. Stoewer -// All rights reserved. - -package strcase - -// KebabCase converts a string into kebab case. -func KebabCase(s string) string { - return delimiterCase(s, '-', false) -} - -// UpperKebabCase converts a string into kebab case with capital letters. -func UpperKebabCase(s string) string { - return delimiterCase(s, '-', true) -} diff --git a/vendor/github.com/stoewer/go-strcase/snake.go b/vendor/github.com/stoewer/go-strcase/snake.go deleted file mode 100644 index 1b216e20c..000000000 --- a/vendor/github.com/stoewer/go-strcase/snake.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2017, A. Stoewer -// All rights reserved. - -package strcase - -import ( - "strings" -) - -// SnakeCase converts a string into snake case. -func SnakeCase(s string) string { - return delimiterCase(s, '_', false) -} - -// UpperSnakeCase converts a string into snake case with capital letters. -func UpperSnakeCase(s string) string { - return delimiterCase(s, '_', true) -} - -// delimiterCase converts a string into snake_case or kebab-case depending on the delimiter passed -// as second argument. When upperCase is true the result will be UPPER_SNAKE_CASE or UPPER-KEBAB-CASE. -func delimiterCase(s string, delimiter rune, upperCase bool) string { - s = strings.TrimSpace(s) - buffer := make([]rune, 0, len(s)+3) - - adjustCase := toLower - if upperCase { - adjustCase = toUpper - } - - var prev rune - var curr rune - for _, next := range s { - if isDelimiter(curr) { - if !isDelimiter(prev) { - buffer = append(buffer, delimiter) - } - } else if isUpper(curr) { - if isLower(prev) || (isUpper(prev) && isLower(next)) { - buffer = append(buffer, delimiter) - } - buffer = append(buffer, adjustCase(curr)) - } else if curr != 0 { - buffer = append(buffer, adjustCase(curr)) - } - prev = curr - curr = next - } - - if len(s) > 0 { - if isUpper(curr) && isLower(prev) && prev != 0 { - buffer = append(buffer, delimiter) - } - buffer = append(buffer, adjustCase(curr)) - } - - return string(buffer) -} diff --git a/vendor/modules.txt b/vendor/modules.txt index f64a35532..0021baba7 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -360,48 +360,51 @@ github.com/go-logr/stdr # github.com/go-logr/zapr v1.3.0 ## explicit; go 1.18 github.com/go-logr/zapr -# github.com/go-openapi/jsonpointer v0.22.0 -## explicit; go 1.20 +# github.com/go-openapi/jsonpointer v0.22.4 +## explicit; go 1.24.0 github.com/go-openapi/jsonpointer -# github.com/go-openapi/jsonreference v0.21.1 -## explicit; go 1.20 +# github.com/go-openapi/jsonreference v0.21.4 +## explicit; go 1.24.0 github.com/go-openapi/jsonreference github.com/go-openapi/jsonreference/internal -# github.com/go-openapi/swag v0.24.1 -## explicit; go 1.20.0 +# github.com/go-openapi/swag v0.25.4 +## explicit; go 1.24.0 github.com/go-openapi/swag -# github.com/go-openapi/swag/cmdutils v0.24.0 -## explicit; go 1.20.0 +# github.com/go-openapi/swag/cmdutils v0.25.4 +## explicit; go 1.24.0 github.com/go-openapi/swag/cmdutils -# github.com/go-openapi/swag/conv v0.24.0 -## explicit; go 1.20.0 +# github.com/go-openapi/swag/conv v0.25.4 +## explicit; go 1.24.0 github.com/go-openapi/swag/conv -# github.com/go-openapi/swag/fileutils v0.24.0 -## explicit; go 1.20.0 +# github.com/go-openapi/swag/fileutils v0.25.4 +## explicit; go 1.24.0 github.com/go-openapi/swag/fileutils -# github.com/go-openapi/swag/jsonname v0.24.0 -## explicit; go 1.20.0 +# github.com/go-openapi/swag/jsonname v0.25.4 +## explicit; go 1.24.0 github.com/go-openapi/swag/jsonname -# github.com/go-openapi/swag/jsonutils v0.24.0 -## explicit; go 1.20 +# github.com/go-openapi/swag/jsonutils v0.25.4 +## explicit; go 1.24.0 github.com/go-openapi/swag/jsonutils -# github.com/go-openapi/swag/loading v0.24.0 -## explicit; go 1.20.0 +github.com/go-openapi/swag/jsonutils/adapters +github.com/go-openapi/swag/jsonutils/adapters/ifaces +github.com/go-openapi/swag/jsonutils/adapters/stdlib/json +# github.com/go-openapi/swag/loading v0.25.4 +## explicit; go 1.24.0 github.com/go-openapi/swag/loading -# github.com/go-openapi/swag/mangling v0.24.0 -## explicit; go 1.20.0 +# github.com/go-openapi/swag/mangling v0.25.4 +## explicit; go 1.24.0 github.com/go-openapi/swag/mangling -# github.com/go-openapi/swag/netutils v0.24.0 -## explicit; go 1.20.0 +# github.com/go-openapi/swag/netutils v0.25.4 +## explicit; go 1.24.0 github.com/go-openapi/swag/netutils -# github.com/go-openapi/swag/stringutils v0.24.0 -## explicit; go 1.20.0 +# github.com/go-openapi/swag/stringutils v0.25.4 +## explicit; go 1.24.0 github.com/go-openapi/swag/stringutils -# github.com/go-openapi/swag/typeutils v0.24.0 -## explicit; go 1.20.0 +# github.com/go-openapi/swag/typeutils v0.25.4 +## explicit; go 1.24.0 github.com/go-openapi/swag/typeutils -# github.com/go-openapi/swag/yamlutils v0.24.0 -## explicit; go 1.20 +# github.com/go-openapi/swag/yamlutils v0.25.4 +## explicit; go 1.24.0 github.com/go-openapi/swag/yamlutils # github.com/go-task/slim-sprig/v3 v3.0.0 ## explicit; go 1.20 @@ -691,9 +694,10 @@ github.com/golangci/unconvert # github.com/google/btree v1.1.3 ## explicit; go 1.18 github.com/google/btree -# github.com/google/cel-go v0.26.0 -## explicit; go 1.22.0 +# github.com/google/cel-go v0.31.0 +## explicit; go 1.23.0 github.com/google/cel-go/cel +github.com/google/cel-go/cel/async github.com/google/cel-go/checker github.com/google/cel-go/checker/decls github.com/google/cel-go/common @@ -716,7 +720,7 @@ github.com/google/cel-go/interpreter github.com/google/cel-go/interpreter/functions github.com/google/cel-go/parser github.com/google/cel-go/parser/gen -# github.com/google/gnostic-models v0.7.0 +# github.com/google/gnostic-models v0.7.1 ## explicit; go 1.22 github.com/google/gnostic-models/compiler github.com/google/gnostic-models/extensions @@ -808,9 +812,6 @@ github.com/jjti/go-spancheck # github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 ## explicit; go 1.14 github.com/jmespath/go-jmespath -# github.com/josharian/intern v1.0.0 -## explicit; go 1.5 -github.com/josharian/intern # github.com/json-iterator/go v1.1.12 ## explicit; go 1.12 github.com/json-iterator/go @@ -873,12 +874,6 @@ github.com/macabu/inamedparam # github.com/magiconair/properties v1.8.6 ## explicit; go 1.13 github.com/magiconair/properties -# github.com/mailru/easyjson v0.9.0 -## explicit; go 1.20 -github.com/mailru/easyjson -github.com/mailru/easyjson/buffer -github.com/mailru/easyjson/jlexer -github.com/mailru/easyjson/jwriter # github.com/manuelarte/embeddedstructfieldcheck v0.4.0 ## explicit; go 1.23.0 github.com/manuelarte/embeddedstructfieldcheck/analyzer @@ -1044,6 +1039,12 @@ github.com/openshift/build-machinery-go/make/targets/openshift/operator github.com/openshift/build-machinery-go/scripts # github.com/openshift/external-secrets-operator v0.0.0-00010101000000-000000000000 => . ## explicit; go 1.26.0 +# github.com/operator-framework/api v0.42.0 +## explicit; go 1.25.7 +github.com/operator-framework/api/pkg/lib/release +github.com/operator-framework/api/pkg/lib/version +github.com/operator-framework/api/pkg/operators +github.com/operator-framework/api/pkg/operators/v1alpha1 # github.com/pelletier/go-toml v1.9.5 ## explicit; go 1.12 github.com/pelletier/go-toml @@ -1159,8 +1160,8 @@ github.com/securego/gosec/v2/rules # github.com/sergi/go-diff v1.2.0 ## explicit; go 1.12 github.com/sergi/go-diff/diffmatchpatch -# github.com/sirupsen/logrus v1.9.3 -## explicit; go 1.13 +# github.com/sirupsen/logrus v1.9.4 +## explicit; go 1.17 github.com/sirupsen/logrus # github.com/sivchari/containedctx v1.0.3 ## explicit; go 1.17 @@ -1205,9 +1206,6 @@ github.com/ssgreg/nlreturn/v2/pkg/nlreturn # github.com/stbenjam/no-sprintf-host-port v0.3.1 ## explicit; go 1.24.0 github.com/stbenjam/no-sprintf-host-port/pkg/analyzer -# github.com/stoewer/go-strcase v1.3.0 -## explicit; go 1.11 -github.com/stoewer/go-strcase # github.com/stretchr/objx v0.5.2 ## explicit; go 1.20 github.com/stretchr/objx @@ -1394,7 +1392,7 @@ go.yaml.in/yaml/v3 ## explicit; go 1.24.0 golang.org/x/crypto/pbkdf2 golang.org/x/crypto/scrypt -# golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 +# golang.org/x/exp v0.0.0-20260112195511-716be5621a96 ## explicit; go 1.24.0 golang.org/x/exp/slices # golang.org/x/exp/typeparams v0.0.0-20251023183803-a4bb9ffd2546 @@ -1607,11 +1605,11 @@ golang.org/x/vuln/scan # gomodules.xyz/jsonpatch/v2 v2.5.0 ## explicit; go 1.20 gomodules.xyz/jsonpatch/v2 -# google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 +# google.golang.org/genproto/googleapis/api v0.0.0-20260202165425-ce8ad4cf556b ## explicit; go 1.24.0 google.golang.org/genproto/googleapis/api/expr/v1alpha1 google.golang.org/genproto/googleapis/api/httpbody -# google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 +# google.golang.org/genproto/googleapis/rpc v0.0.0-20260202165425-ce8ad4cf556b ## explicit; go 1.24.0 google.golang.org/genproto/googleapis/rpc/errdetails google.golang.org/genproto/googleapis/rpc/status @@ -1993,7 +1991,7 @@ k8s.io/api/storage/v1 k8s.io/api/storage/v1alpha1 k8s.io/api/storage/v1beta1 k8s.io/api/storagemigration/v1beta1 -# k8s.io/apiextensions-apiserver v0.35.0 +# k8s.io/apiextensions-apiserver v0.35.3 ## explicit; go 1.25.0 k8s.io/apiextensions-apiserver/pkg/apis/apiextensions k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1 @@ -2403,7 +2401,7 @@ k8s.io/client-go/util/keyutil k8s.io/client-go/util/retry k8s.io/client-go/util/watchlist k8s.io/client-go/util/workqueue -# k8s.io/code-generator v0.35.0 +# k8s.io/code-generator v0.35.3 ## explicit; go 1.25.0 k8s.io/code-generator/cmd/applyconfiguration-gen/args k8s.io/code-generator/cmd/applyconfiguration-gen/generators