diff --git a/go.mod b/go.mod index 6d02b8fb44df7..a959df1d227d7 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/godbus/dbus/v5 v5.1.0 github.com/gogo/protobuf v1.3.2 github.com/google/cadvisor v0.52.1 - github.com/google/cel-go v0.23.2 + github.com/google/cel-go v0.30.0 github.com/google/gnostic-models v0.6.9 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 @@ -129,7 +129,7 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect - github.com/antlr4-go/antlr/v4 v4.13.0 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -193,7 +193,6 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/soheilhy/cmux v0.1.5 // indirect - github.com/stoewer/go-strcase v1.3.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 // indirect github.com/x448/float16 v0.8.4 // indirect @@ -208,7 +207,8 @@ require ( go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect golang.org/x/mod v0.38.0 // indirect golang.org/x/text v0.41.0 // indirect google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect diff --git a/go.sum b/go.sum index c9ee0afd3c312..8c532a8f0f18a 100644 --- a/go.sum +++ b/go.sum @@ -4,127 +4,9 @@ cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= -cloud.google.com/go/accessapproval v1.7.4/go.mod h1:/aTEh45LzplQgFYdQdwPMR9YdX0UlhBmvB84uAmQKUc= -cloud.google.com/go/accesscontextmanager v1.8.4/go.mod h1:ParU+WbMpD34s5JFEnGAnPBYAgUHozaTmDJU7aCU9+M= -cloud.google.com/go/aiplatform v1.58.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= -cloud.google.com/go/analytics v0.22.0/go.mod h1:eiROFQKosh4hMaNhF85Oc9WO97Cpa7RggD40e/RBy8w= -cloud.google.com/go/apigateway v1.6.4/go.mod h1:0EpJlVGH5HwAN4VF4Iec8TAzGN1aQgbxAWGJsnPCGGY= -cloud.google.com/go/apigeeconnect v1.6.4/go.mod h1:CapQCWZ8TCjnU0d7PobxhpOdVz/OVJ2Hr/Zcuu1xFx0= -cloud.google.com/go/apigeeregistry v0.8.2/go.mod h1:h4v11TDGdeXJDJvImtgK2AFVvMIgGWjSb0HRnBSjcX8= -cloud.google.com/go/appengine v1.8.4/go.mod h1:TZ24v+wXBujtkK77CXCpjZbnuTvsFNT41MUaZ28D6vg= -cloud.google.com/go/area120 v0.8.4/go.mod h1:jfawXjxf29wyBXr48+W+GyX/f8fflxp642D/bb9v68M= -cloud.google.com/go/artifactregistry v1.14.6/go.mod h1:np9LSFotNWHcjnOgh8UVK0RFPCTUGbO0ve3384xyHfE= -cloud.google.com/go/asset v1.17.0/go.mod h1:yYLfUD4wL4X589A9tYrv4rFrba0QlDeag0CMcM5ggXU= -cloud.google.com/go/assuredworkloads v1.11.4/go.mod h1:4pwwGNwy1RP0m+y12ef3Q/8PaiWrIDQ6nD2E8kvWI9U= -cloud.google.com/go/automl v1.13.4/go.mod h1:ULqwX/OLZ4hBVfKQaMtxMSTlPx0GqGbWN8uA/1EqCP8= -cloud.google.com/go/baremetalsolution v1.2.3/go.mod h1:/UAQ5xG3faDdy180rCUv47e0jvpp3BFxT+Cl0PFjw5g= -cloud.google.com/go/batch v1.7.0/go.mod h1:J64gD4vsNSA2O5TtDB5AAux3nJ9iV8U3ilg3JDBYejU= -cloud.google.com/go/beyondcorp v1.0.3/go.mod h1:HcBvnEd7eYr+HGDd5ZbuVmBYX019C6CEXBonXbCVwJo= -cloud.google.com/go/bigquery v1.58.0/go.mod h1:0eh4mWNY0KrBTjUzLjoYImapGORq9gEPT7MWjCy9lik= -cloud.google.com/go/billing v1.18.0/go.mod h1:5DOYQStCxquGprqfuid/7haD7th74kyMBHkjO/OvDtk= -cloud.google.com/go/binaryauthorization v1.8.0/go.mod h1:VQ/nUGRKhrStlGr+8GMS8f6/vznYLkdK5vaKfdCIpvU= -cloud.google.com/go/certificatemanager v1.7.4/go.mod h1:FHAylPe/6IIKuaRmHbjbdLhGhVQ+CWHSD5Jq0k4+cCE= -cloud.google.com/go/channel v1.17.4/go.mod h1:QcEBuZLGGrUMm7kNj9IbU1ZfmJq2apotsV83hbxX7eE= -cloud.google.com/go/cloudbuild v1.15.0/go.mod h1:eIXYWmRt3UtggLnFGx4JvXcMj4kShhVzGndL1LwleEM= -cloud.google.com/go/clouddms v1.7.3/go.mod h1:fkN2HQQNUYInAU3NQ3vRLkV2iWs8lIdmBKOx4nrL6Hc= -cloud.google.com/go/cloudtasks v1.12.4/go.mod h1:BEPu0Gtt2dU6FxZHNqqNdGqIG86qyWKBPGnsb7udGY0= -cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= -cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= -cloud.google.com/go/contactcenterinsights v1.12.1/go.mod h1:HHX5wrz5LHVAwfI2smIotQG9x8Qd6gYilaHcLLLmNis= -cloud.google.com/go/container v1.29.0/go.mod h1:b1A1gJeTBXVLQ6GGw9/9M4FG94BEGsqJ5+t4d/3N7O4= -cloud.google.com/go/containeranalysis v0.11.3/go.mod h1:kMeST7yWFQMGjiG9K7Eov+fPNQcGhb8mXj/UcTiWw9U= -cloud.google.com/go/datacatalog v1.19.2/go.mod h1:2YbODwmhpLM4lOFe3PuEhHK9EyTzQJ5AXgIy7EDKTEE= -cloud.google.com/go/dataflow v0.9.4/go.mod h1:4G8vAkHYCSzU8b/kmsoR2lWyHJD85oMJPHMtan40K8w= -cloud.google.com/go/dataform v0.9.1/go.mod h1:pWTg+zGQ7i16pyn0bS1ruqIE91SdL2FDMvEYu/8oQxs= -cloud.google.com/go/datafusion v1.7.4/go.mod h1:BBs78WTOLYkT4GVZIXQCZT3GFpkpDN4aBY4NDX/jVlM= -cloud.google.com/go/datalabeling v0.8.4/go.mod h1:Z1z3E6LHtffBGrNUkKwbwbDxTiXEApLzIgmymj8A3S8= -cloud.google.com/go/dataplex v1.14.0/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= -cloud.google.com/go/dataproc/v2 v2.3.0/go.mod h1:G5R6GBc9r36SXv/RtZIVfB8SipI+xVn0bX5SxUzVYbY= -cloud.google.com/go/dataqna v0.8.4/go.mod h1:mySRKjKg5Lz784P6sCov3p1QD+RZQONRMRjzGNcFd0c= -cloud.google.com/go/datastore v1.15.0/go.mod h1:GAeStMBIt9bPS7jMJA85kgkpsMkvseWWXiaHya9Jes8= -cloud.google.com/go/datastream v1.10.3/go.mod h1:YR0USzgjhqA/Id0Ycu1VvZe8hEWwrkjuXrGbzeDOSEA= -cloud.google.com/go/deploy v1.17.0/go.mod h1:XBr42U5jIr64t92gcpOXxNrqL2PStQCXHuKK5GRUuYo= -cloud.google.com/go/dialogflow v1.48.1/go.mod h1:C1sjs2/g9cEwjCltkKeYp3FFpz8BOzNondEaAlCpt+A= -cloud.google.com/go/dlp v1.11.1/go.mod h1:/PA2EnioBeXTL/0hInwgj0rfsQb3lpE3R8XUJxqUNKI= -cloud.google.com/go/documentai v1.23.7/go.mod h1:ghzBsyVTiVdkfKaUCum/9bGBEyBjDO4GfooEcYKhN+g= -cloud.google.com/go/domains v0.9.4/go.mod h1:27jmJGShuXYdUNjyDG0SodTfT5RwLi7xmH334Gvi3fY= -cloud.google.com/go/edgecontainer v1.1.4/go.mod h1:AvFdVuZuVGdgaE5YvlL1faAoa1ndRR/5XhXZvPBHbsE= -cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= -cloud.google.com/go/essentialcontacts v1.6.5/go.mod h1:jjYbPzw0x+yglXC890l6ECJWdYeZ5dlYACTFL0U/VuM= -cloud.google.com/go/eventarc v1.13.3/go.mod h1:RWH10IAZIRcj1s/vClXkBgMHwh59ts7hSWcqD3kaclg= -cloud.google.com/go/filestore v1.8.0/go.mod h1:S5JCxIbFjeBhWMTfIYH2Jx24J6BqjwpkkPl+nBA5DlI= -cloud.google.com/go/firestore v1.14.0/go.mod h1:96MVaHLsEhbvkBEdZgfN+AS/GIkco1LRpH9Xp9YZfzQ= -cloud.google.com/go/functions v1.15.4/go.mod h1:CAsTc3VlRMVvx+XqXxKqVevguqJpnVip4DdonFsX28I= -cloud.google.com/go/gkebackup v1.3.4/go.mod h1:gLVlbM8h/nHIs09ns1qx3q3eaXcGSELgNu1DWXYz1HI= -cloud.google.com/go/gkeconnect v0.8.4/go.mod h1:84hZz4UMlDCKl8ifVW8layK4WHlMAFeq8vbzjU0yJkw= -cloud.google.com/go/gkehub v0.14.4/go.mod h1:Xispfu2MqnnFt8rV/2/3o73SK1snL8s9dYJ9G2oQMfc= -cloud.google.com/go/gkemulticloud v1.1.0/go.mod h1:7NpJBN94U6DY1xHIbsDqB2+TFZUfjLUKLjUX8NGLor0= -cloud.google.com/go/gsuiteaddons v1.6.4/go.mod h1:rxtstw7Fx22uLOXBpsvb9DUbC+fiXs7rF4U29KHM/pE= -cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= -cloud.google.com/go/iap v1.9.3/go.mod h1:DTdutSZBqkkOm2HEOTBzhZxh2mwwxshfD/h3yofAiCw= -cloud.google.com/go/ids v1.4.4/go.mod h1:z+WUc2eEl6S/1aZWzwtVNWoSZslgzPxAboS0lZX0HjI= -cloud.google.com/go/iot v1.7.4/go.mod h1:3TWqDVvsddYBG++nHSZmluoCAVGr1hAcabbWZNKEZLk= -cloud.google.com/go/kms v1.15.5/go.mod h1:cU2H5jnp6G2TDpUGZyqTCoy1n16fbubHZjmVXSMtwDI= -cloud.google.com/go/language v1.12.2/go.mod h1:9idWapzr/JKXBBQ4lWqVX/hcadxB194ry20m/bTrhWc= -cloud.google.com/go/lifesciences v0.9.4/go.mod h1:bhm64duKhMi7s9jR9WYJYvjAFJwRqNj+Nia7hF0Z7JA= -cloud.google.com/go/logging v1.9.0/go.mod h1:1Io0vnZv4onoUnsVUQY3HZ3Igb1nBchky0A0y7BBBhE= -cloud.google.com/go/longrunning v0.5.4/go.mod h1:zqNVncI0BOP8ST6XQD1+VcvuShMmq7+xFSzOL++V0dI= -cloud.google.com/go/managedidentities v1.6.4/go.mod h1:WgyaECfHmF00t/1Uk8Oun3CQ2PGUtjc3e9Alh79wyiM= -cloud.google.com/go/maps v1.6.3/go.mod h1:VGAn809ADswi1ASofL5lveOHPnE6Rk/SFTTBx1yuOLw= -cloud.google.com/go/mediatranslation v0.8.4/go.mod h1:9WstgtNVAdN53m6TQa5GjIjLqKQPXe74hwSCxUP6nj4= -cloud.google.com/go/memcache v1.10.4/go.mod h1:v/d8PuC8d1gD6Yn5+I3INzLR01IDn0N4Ym56RgikSI0= -cloud.google.com/go/metastore v1.13.3/go.mod h1:K+wdjXdtkdk7AQg4+sXS8bRrQa9gcOr+foOMF2tqINE= -cloud.google.com/go/monitoring v1.17.0/go.mod h1:KwSsX5+8PnXv5NJnICZzW2R8pWTis8ypC4zmdRD63Tw= -cloud.google.com/go/networkconnectivity v1.14.3/go.mod h1:4aoeFdrJpYEXNvrnfyD5kIzs8YtHg945Og4koAjHQek= -cloud.google.com/go/networkmanagement v1.9.3/go.mod h1:y7WMO1bRLaP5h3Obm4tey+NquUvB93Co1oh4wpL+XcU= -cloud.google.com/go/networksecurity v0.9.4/go.mod h1:E9CeMZ2zDsNBkr8axKSYm8XyTqNhiCHf1JO/Vb8mD1w= -cloud.google.com/go/notebooks v1.11.2/go.mod h1:z0tlHI/lREXC8BS2mIsUeR3agM1AkgLiS+Isov3SS70= -cloud.google.com/go/optimization v1.6.2/go.mod h1:mWNZ7B9/EyMCcwNl1frUGEuY6CPijSkz88Fz2vwKPOY= -cloud.google.com/go/orchestration v1.8.4/go.mod h1:d0lywZSVYtIoSZXb0iFjv9SaL13PGyVOKDxqGxEf/qI= -cloud.google.com/go/orgpolicy v1.12.0/go.mod h1:0+aNV/nrfoTQ4Mytv+Aw+stBDBjNf4d8fYRA9herfJI= -cloud.google.com/go/osconfig v1.12.4/go.mod h1:B1qEwJ/jzqSRslvdOCI8Kdnp0gSng0xW4LOnIebQomA= -cloud.google.com/go/oslogin v1.13.0/go.mod h1:xPJqLwpTZ90LSE5IL1/svko+6c5avZLluiyylMb/sRA= -cloud.google.com/go/phishingprotection v0.8.4/go.mod h1:6b3kNPAc2AQ6jZfFHioZKg9MQNybDg4ixFd4RPZZ2nE= -cloud.google.com/go/policytroubleshooter v1.10.2/go.mod h1:m4uF3f6LseVEnMV6nknlN2vYGRb+75ylQwJdnOXfnv0= -cloud.google.com/go/privatecatalog v0.9.4/go.mod h1:SOjm93f+5hp/U3PqMZAHTtBtluqLygrDrVO8X8tYtG0= -cloud.google.com/go/pubsub v1.34.0/go.mod h1:alj4l4rBg+N3YTFDDC+/YyFTs6JAjam2QfYsddcAW4c= -cloud.google.com/go/pubsublite v1.8.1/go.mod h1:fOLdU4f5xldK4RGJrBMm+J7zMWNj/k4PxwEZXy39QS0= -cloud.google.com/go/recaptchaenterprise/v2 v2.9.0/go.mod h1:Dak54rw6lC2gBY8FBznpOCAR58wKf+R+ZSJRoeJok4w= -cloud.google.com/go/recommendationengine v0.8.4/go.mod h1:GEteCf1PATl5v5ZsQ60sTClUE0phbWmo3rQ1Js8louU= -cloud.google.com/go/recommender v1.12.0/go.mod h1:+FJosKKJSId1MBFeJ/TTyoGQZiEelQQIZMKYYD8ruK4= -cloud.google.com/go/redis v1.14.1/go.mod h1:MbmBxN8bEnQI4doZPC1BzADU4HGocHBk2de3SbgOkqs= -cloud.google.com/go/resourcemanager v1.9.4/go.mod h1:N1dhP9RFvo3lUfwtfLWVxfUWq8+KUQ+XLlHLH3BoFJ0= -cloud.google.com/go/resourcesettings v1.6.4/go.mod h1:pYTTkWdv2lmQcjsthbZLNBP4QW140cs7wqA3DuqErVI= -cloud.google.com/go/retail v1.14.4/go.mod h1:l/N7cMtY78yRnJqp5JW8emy7MB1nz8E4t2yfOmklYfg= -cloud.google.com/go/run v1.3.3/go.mod h1:WSM5pGyJ7cfYyYbONVQBN4buz42zFqwG67Q3ch07iK4= -cloud.google.com/go/scheduler v1.10.5/go.mod h1:MTuXcrJC9tqOHhixdbHDFSIuh7xZF2IysiINDuiq6NI= -cloud.google.com/go/secretmanager v1.11.4/go.mod h1:wreJlbS9Zdq21lMzWmJ0XhWW2ZxgPeahsqeV/vZoJ3w= -cloud.google.com/go/security v1.15.4/go.mod h1:oN7C2uIZKhxCLiAAijKUCuHLZbIt/ghYEo8MqwD/Ty4= -cloud.google.com/go/securitycenter v1.24.3/go.mod h1:l1XejOngggzqwr4Fa2Cn+iWZGf+aBLTXtB/vXjy5vXM= -cloud.google.com/go/servicedirectory v1.11.3/go.mod h1:LV+cHkomRLr67YoQy3Xq2tUXBGOs5z5bPofdq7qtiAw= -cloud.google.com/go/shell v1.7.4/go.mod h1:yLeXB8eKLxw0dpEmXQ/FjriYrBijNsONpwnWsdPqlKM= -cloud.google.com/go/spanner v1.55.0/go.mod h1:HXEznMUVhC+PC+HDyo9YFG2Ajj5BQDkcbqB9Z2Ffxi0= -cloud.google.com/go/speech v1.21.0/go.mod h1:wwolycgONvfz2EDU8rKuHRW3+wc9ILPsAWoikBEWavY= -cloud.google.com/go/storagetransfer v1.10.3/go.mod h1:Up8LY2p6X68SZ+WToswpQbQHnJpOty/ACcMafuey8gc= -cloud.google.com/go/talent v1.6.5/go.mod h1:Mf5cma696HmE+P2BWJ/ZwYqeJXEeU0UqjHFXVLadEDI= -cloud.google.com/go/texttospeech v1.7.4/go.mod h1:vgv0002WvR4liGuSd5BJbWy4nDn5Ozco0uJymY5+U74= -cloud.google.com/go/tpu v1.6.4/go.mod h1:NAm9q3Rq2wIlGnOhpYICNI7+bpBebMJbh0yyp3aNw1Y= -cloud.google.com/go/trace v1.10.4/go.mod h1:Nso99EDIK8Mj5/zmB+iGr9dosS/bzWCJ8wGmE6TXNWY= -cloud.google.com/go/translate v1.10.0/go.mod h1:Kbq9RggWsbqZ9W5YpM94Q1Xv4dshw/gr/SHfsl5yCZ0= -cloud.google.com/go/video v1.20.3/go.mod h1:TnH/mNZKVHeNtpamsSPygSR0iHtvrR/cW1/GDjN5+GU= -cloud.google.com/go/videointelligence v1.11.4/go.mod h1:kPBMAYsTPFiQxMLmmjpcZUMklJp3nC9+ipJJtprccD8= -cloud.google.com/go/vision/v2 v2.7.5/go.mod h1:GcviprJLFfK9OLf0z8Gm6lQb6ZFUulvpZws+mm6yPLM= -cloud.google.com/go/vmmigration v1.7.4/go.mod h1:yBXCmiLaB99hEl/G9ZooNx2GyzgsjKnw5fWcINRgD70= -cloud.google.com/go/vmwareengine v1.0.3/go.mod h1:QSpdZ1stlbfKtyt6Iu19M6XRxjmXO+vb5a/R6Fvy2y4= -cloud.google.com/go/vpcaccess v1.7.4/go.mod h1:lA0KTvhtEOb/VOdnH/gwPuOzGgM+CWsmGu6bb4IoMKk= -cloud.google.com/go/webrisk v1.9.4/go.mod h1:w7m4Ib4C+OseSr2GL66m0zMBywdrVNTDKsdEsfMl7X0= -cloud.google.com/go/websecurityscanner v1.6.4/go.mod h1:mUiyMQ+dGpPPRkHgknIZeCzSHJ45+fY4F52nZFDHm2o= -cloud.google.com/go/workflows v1.12.3/go.mod h1:fmOUeeqEwPzIU81foMjTRQIdwQHADi/vEr1cx9R1m5g= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab h1:UKkYhof1njT1/xq4SEg5z+VpTgjmNeHwPGRQl7takDI= github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab/go.mod h1:3VYc5hodBMJ5+l/7J4xAyMeuM2PNuepvHlGs8yilUCA= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= @@ -135,28 +17,13 @@ github.com/Microsoft/hnslib v0.1.1 h1:JsZy681SnvSOUAfCZVAxkX4LgQGp+CZZwPbLV0/pdF github.com/Microsoft/hnslib v0.1.1/go.mod h1:DRQR4IjLae6WHYVhW7uqe44hmFUiNhmaWA+jwMbz5tM= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= -github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 h1:7Ip0wMmLHLRJdrloDxZfhMm0xrLXZS8+COSu2bXmEQs= github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/aws/aws-sdk-go-v2 v1.30.1/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= -github.com/aws/aws-sdk-go-v2/config v1.27.24/go.mod h1:aXzi6QJTuQRVVusAO8/NxpdTeTyr/wRcybdDtfUwJSs= -github.com/aws/aws-sdk-go-v2/credentials v1.17.24/go.mod h1:Hld7tmnAkoBQdTMNYZGzztzKRdA4fCdn9L83LOoigac= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9/go.mod h1:WQr3MY7AxGNxaqAtsDWn+fBxmd4XvLkzeqQ8P1VM0/w= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13/go.mod h1:+rdA6ZLpaSeM7tSg/B0IEDinCIBJGmW8rKDFkYpP04g= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13/go.mod h1:i+kbfa76PQbWw/ULoWnp51EYVWH4ENln76fLQE3lXT8= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15/go.mod h1:9xWJ3Q/S6Ojusz1UIkfycgD1mGirJfLLKqq3LPT7WN8= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.1/go.mod h1:/vWdhoIoYA5hYoPZ6fm7Sv4d8701PiG5VKe8/pPJL60= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2/go.mod h1:xyFHA4zGxgYkdD73VeezHt3vSKEG9EmFnGwoKlP00u4= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.1/go.mod h1:jiNR3JqT15Dm+QWq2SRgh0x0bCNSRP2L25+CqPNpJlQ= -github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= @@ -168,11 +35,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= -github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= -github.com/cilium/ebpf v0.17.3/go.mod h1:G5EDHij8yiLzaqn0WjyfJHvRa+3aDlReIaLVRMvOyJk= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/container-storage-interface/spec v1.9.0 h1:zKtX4STsq31Knz3gciCYCi1SXtO2HJDecIjDVboYavY= @@ -225,11 +89,7 @@ github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRr github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= -github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= -github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= github.com/euank/go-kmsg-parser v2.0.0+incompatible h1:cHD53+PLQuuQyLZeriD1V/esuG4MuU0Pjs5y6iknohY= github.com/euank/go-kmsg-parser v2.0.0+incompatible/go.mod h1:MhmAMZ8V4CYH4ybgdRwPr2TU5ThnS43puaKEMpja1uw= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= @@ -246,7 +106,6 @@ github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXE github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -275,8 +134,6 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -287,8 +144,8 @@ 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/cadvisor v0.52.1 h1:sC8SZ6jio9ds+P2dk51bgbeYeufxo55n0X3tmrpA9as= github.com/google/cadvisor v0.52.1/go.mod h1:OAhPcx1nOm5YwMh/JhpUOMKyv1YKLRtS9KgzWPndHmA= -github.com/google/cel-go v0.23.2 h1:UdEe3CvQh3Nv+E/j9r1Y//WO0K0cSyD7/y0bzyLIMI4= -github.com/google/cel-go v0.23.2/go.mod h1:52Pb6QsDbC5kvgxvZhiL9QX1oZEkcUF/ZqaPx1J5Wwo= +github.com/google/cel-go v0.30.0 h1:ll54AkzKunWkBn9wSoiUXbFZXYZTkdJGNXTBXUoolGo= +github.com/google/cel-go v0.30.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -318,7 +175,6 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= -github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/ishidawataru/sctp v0.0.0-20230406120618-7ff4192f6ff2 h1:i2fYnDurfLlJH8AyyMOnkLHnHeP8Ff/DDpuZA/D3bPo= @@ -327,10 +183,8 @@ github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= 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/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 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/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/karrick/godirwalk v1.17.0 h1:b4kY7nqDdioR/6qnbHQyDvmA17u5G1cZ6J+CZXwSWoI= github.com/karrick/godirwalk v1.17.0/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -356,7 +210,6 @@ github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffkt github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible h1:aKW/4cBs+yK6gpqU3K/oIwk9Q/XICqd3zOX/UFuvqmk= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -382,12 +235,10 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mrunalp/fileutils v0.5.1 h1:F+S7ZlNKnrwHfSwdlgNSkKo67ReVf8o9fel6C3dkm/Q= github.com/mrunalp/fileutils v0.5.1/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= @@ -400,7 +251,6 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/opencontainers/runc v1.2.5/go.mod h1:dOQeFo29xZKBNeRBI0B19mJtfHv68YgCTh1X+YphA+4= github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/selinux v1.13.1 h1:A8nNeceYngH9Ow++M+VVEwJVpdFmrlxsN22F+ISDCJE= @@ -411,7 +261,6 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -431,10 +280,8 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 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/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= 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/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -447,9 +294,6 @@ github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= -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.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -474,14 +318,12 @@ github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1Y github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk= github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.etcd.io/etcd/api/v3 v3.5.21 h1:A6O2/JDb3tvHhiIz3xf9nJ7REHvtEFJJ3veW3FbCnS8= @@ -498,10 +340,8 @@ go.etcd.io/etcd/raft/v3 v3.5.21 h1:dOmE0mT55dIUsX77TKBLq+RgyumsQuYeiRQnW/ylugk= go.etcd.io/etcd/raft/v3 v3.5.21/go.mod h1:fmcuY5R2SNkklU4+fKVBQi2biVp5vafMrWUEj4TJ4Cs= go.etcd.io/etcd/server/v3 v3.5.21 h1:9w0/k12majtgarGmlMVuhwXRI2ob3/d1Ik3X5TKo0yU= go.etcd.io/etcd/server/v3 v3.5.21/go.mod h1:G1mOzdwuzKT1VRL7SqRchli/qcFrtLBTAQ4lV20sXXo= -go.etcd.io/gofail v0.1.0/go.mod h1:VZBCXYGZhHAinaBiiqYvuDynvahNsAyLFwB3kEHKz1M= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk= go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful v0.44.0 h1:KemlMZlVwBSEGaO91WKgp41BBFsnWqqj9sKRwmOqC40= go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful v0.44.0/go.mod h1:uq8DrRaen3suIWTpdR/JNHCGpurSvMv9D5Nr5CU5TXc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc= @@ -516,7 +356,6 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 h1:5pojmb1U1AogINhN3SurB+zm/nIcusopeBNp42f45QM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0/go.mod h1:HVkSiDhTM9BoUJU8qE6j2eSWLLXvi1USXjyd2BXT8PY= go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= @@ -528,7 +367,6 @@ go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLh go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= @@ -537,14 +375,16 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +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/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA= +golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -594,7 +434,6 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/telemetry v0.0.0-20260708182218-49f421fb7959/go.mod h1:LV7u5Oco+Z/g6XI7PqN+EUUUGGkEcmB1uj2ceI0fOVg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= @@ -660,11 +499,9 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7 h1:2OX19X59HxDprNCVrWi6jb7LW1PoqTlYqEq5H2oetog= @@ -685,7 +522,6 @@ sigs.k8s.io/knftables v0.0.17 h1:wGchTyRF/iGTIjd+vRaR1m676HM7jB8soFtyr/148ic= sigs.k8s.io/knftables v0.0.17/go.mod h1:f/5ZLKYEUPUhVjUCg6l80ACdL7CIIyeL0DxfgojGRTk= sigs.k8s.io/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ= sigs.k8s.io/kustomize/api v0.19.0/go.mod h1:/BbwnivGVcBh1r+8m3tH1VNxJmHSk1PzP5fkP6lbL1o= -sigs.k8s.io/kustomize/cmd/config v0.19.0/go.mod h1:29Vvdl26PidPLUDi7nfjYa/I0wHBkwCZp15Nlcc4y98= sigs.k8s.io/kustomize/kustomize/v5 v5.6.0 h1:MWtRRDWCwQEeW2rnJTqJMuV6Agy56P53SkbVoJpN7wA= sigs.k8s.io/kustomize/kustomize/v5 v5.6.0/go.mod h1:XuuZiQF7WdcvZzEYyNww9A0p3LazCKeJmCjeycN8e1I= sigs.k8s.io/kustomize/kyaml v0.19.0 h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XSlA= diff --git a/go.work.sum b/go.work.sum index e2175e9d924e8..bbe4468a49bff 100644 --- a/go.work.sum +++ b/go.work.sum @@ -176,12 +176,36 @@ github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8 github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= go.etcd.io/gofail v0.1.0 h1:XItAMIhOojXFQMgrxjnd2EIIHun/d5qL0Pf7FzVTkFg= go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 h1:QY7/0NeRPKlzusf40ZE4t1VlMKbqSNT7cJRYzWuja0s= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20260708182218-49f421fb7959 h1:RJhm5l6Fo4rmEIcndxDllNhhf/fAx8qIm4t6A7vpm2A= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= +google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs= diff --git a/vendor/OWNERS b/vendor/OWNERS deleted file mode 100644 index 163a7393ee884..0000000000000 --- a/vendor/OWNERS +++ /dev/null @@ -1,9 +0,0 @@ -# See the OWNERS docs at https://go.k8s.io/owners - -options: - # make root approval non-recursive - no_parent_owners: true -approvers: -- dep-approvers -reviewers: -- dep-reviewers diff --git a/vendor/github.com/antlr4-go/antlr/v4/antlrdoc.go b/vendor/github.com/antlr4-go/antlr/v4/antlrdoc.go index 3bb4fd7c4e0af..48bd362bf53fb 100644 --- a/vendor/github.com/antlr4-go/antlr/v4/antlrdoc.go +++ b/vendor/github.com/antlr4-go/antlr/v4/antlrdoc.go @@ -17,9 +17,9 @@ ANTLR4 that it is compatible with (I.E. uses the /v4 path). However, this was found to be problematic, as it meant that with the runtime embedded so far underneath the root of the repo, the `go get` and related commands could not properly resolve the location of the go runtime source code. This meant that the reference to the runtime in your `go.mod` file would refer to the correct source code, but would not -list the release tag such as @4.12.0 - this was confusing, to say the least. +list the release tag such as @4.13.1 - this was confusing, to say the least. -As of 4.12.1, the runtime is now available as a go module in its own repo, and can be imported as `github.com/antlr4-go/antlr` +As of 4.13.0, the runtime is now available as a go module in its own repo, and can be imported as `github.com/antlr4-go/antlr` (the go get command should also be used with this path). See the main documentation for the ANTLR4 project for more information, which is available at [ANTLR docs]. The documentation for using the Go runtime is available at [Go runtime docs]. @@ -49,7 +49,7 @@ Here is a general/recommended template for an ANTLR based recognizer in Go: . ├── parser │ ├── mygrammar.g4 - │ ├── antlr-4.12.1-complete.jar + │ ├── antlr-4.13.1-complete.jar │ ├── generate.go │ └── generate.sh ├── parsing - generated code goes here @@ -71,7 +71,7 @@ And the generate.sh file will look similar to this: #!/bin/sh - alias antlr4='java -Xmx500M -cp "./antlr4-4.12.1-complete.jar:$CLASSPATH" org.antlr.v4.Tool' + alias antlr4='java -Xmx500M -cp "./antlr4-4.13.1-complete.jar:$CLASSPATH" org.antlr.v4.Tool' antlr4 -Dlanguage=Go -no-visitor -package parsing *.g4 depending on whether you want visitors or listeners or any other ANTLR options. Not that another option here diff --git a/vendor/github.com/antlr4-go/antlr/v4/atn.go b/vendor/github.com/antlr4-go/antlr/v4/atn.go index cdeefed247892..e749ebd0cfda3 100644 --- a/vendor/github.com/antlr4-go/antlr/v4/atn.go +++ b/vendor/github.com/antlr4-go/antlr/v4/atn.go @@ -4,8 +4,6 @@ package antlr -import "sync" - // ATNInvalidAltNumber is used to represent an ALT number that has yet to be calculated or // which is invalid for a particular struct such as [*antlr.BaseRuleContext] var ATNInvalidAltNumber int @@ -56,9 +54,9 @@ type ATN struct { // states []ATNState - mu sync.Mutex - stateMu sync.RWMutex - edgeMu sync.RWMutex + mu Mutex + stateMu RWMutex + edgeMu RWMutex } // NewATN returns a new ATN struct representing the given grammarType and is used diff --git a/vendor/github.com/antlr4-go/antlr/v4/atn_config.go b/vendor/github.com/antlr4-go/antlr/v4/atn_config.go index a83f25d3492e6..267308bb3d00f 100644 --- a/vendor/github.com/antlr4-go/antlr/v4/atn_config.go +++ b/vendor/github.com/antlr4-go/antlr/v4/atn_config.go @@ -73,9 +73,6 @@ func NewATNConfig1(c *ATNConfig, state ATNState, context *PredictionContext) *AT // NewATNConfig creates a new ATNConfig instance given an existing config, a state, a context and a semantic context, other 'constructors' // are just wrappers around this one. func NewATNConfig(c *ATNConfig, state ATNState, context *PredictionContext, semanticContext SemanticContext) *ATNConfig { - if semanticContext == nil { - panic("semanticContext cannot be nil") // TODO: Remove this - probably put here for some bug that is now fixed - } b := &ATNConfig{} b.InitATNConfig(c, state, c.GetAlt(), context, semanticContext) b.cType = parserConfig diff --git a/vendor/github.com/antlr4-go/antlr/v4/input_stream.go b/vendor/github.com/antlr4-go/antlr/v4/input_stream.go index b737fe85fbd02..ab4e96be524ae 100644 --- a/vendor/github.com/antlr4-go/antlr/v4/input_stream.go +++ b/vendor/github.com/antlr4-go/antlr/v4/input_stream.go @@ -148,7 +148,7 @@ func (is *InputStream) GetTextFromInterval(i Interval) string { } func (*InputStream) GetSourceName() string { - return "" + return "Obtained from string" } // String returns the entire input stream as a string diff --git a/vendor/github.com/antlr4-go/antlr/v4/jcollect.go b/vendor/github.com/antlr4-go/antlr/v4/jcollect.go index ceccd96d25844..6d668f798360d 100644 --- a/vendor/github.com/antlr4-go/antlr/v4/jcollect.go +++ b/vendor/github.com/antlr4-go/antlr/v4/jcollect.go @@ -8,7 +8,6 @@ import ( "container/list" "runtime/debug" "sort" - "sync" ) // Collectable is an interface that a struct should implement if it is to be @@ -587,12 +586,12 @@ type VisitRecord struct { type VisitList struct { cache *list.List - lock sync.RWMutex + lock RWMutex } var visitListPool = VisitList{ cache: list.New(), - lock: sync.RWMutex{}, + lock: RWMutex{}, } // NewVisitRecord returns a new VisitRecord instance from the pool if available. diff --git a/vendor/github.com/antlr4-go/antlr/v4/lexer.go b/vendor/github.com/antlr4-go/antlr/v4/lexer.go index 3c7896a9183df..e5594b2168a1b 100644 --- a/vendor/github.com/antlr4-go/antlr/v4/lexer.go +++ b/vendor/github.com/antlr4-go/antlr/v4/lexer.go @@ -207,7 +207,7 @@ func (b *BaseLexer) NextToken() Token { for { b.thetype = TokenInvalidType - ttype := b.safeMatch() + ttype := b.safeMatch() // Defaults to LexerSkip if b.input.LA(1) == TokenEOF { b.hitEOF = true diff --git a/vendor/github.com/antlr4-go/antlr/v4/ll1_analyzer.go b/vendor/github.com/antlr4-go/antlr/v4/ll1_analyzer.go index 4955ac876f97e..dfdff000bc243 100644 --- a/vendor/github.com/antlr4-go/antlr/v4/ll1_analyzer.go +++ b/vendor/github.com/antlr4-go/antlr/v4/ll1_analyzer.go @@ -40,6 +40,7 @@ func (la *LL1Analyzer) getDecisionLookahead(s ATNState) []*IntervalSet { for alt := 0; alt < count; alt++ { look[alt] = NewIntervalSet() + // TODO: This is one of the reasons that ATNConfigs are allocated and freed all the time - fix this tomorrow jim! lookBusy := NewJStore[*ATNConfig, Comparator[*ATNConfig]](aConfEqInst, ClosureBusyCollection, "LL1Analyzer.getDecisionLookahead for lookBusy") la.look1(s.GetTransitions()[alt].getTarget(), nil, BasePredictionContextEMPTY, look[alt], lookBusy, NewBitSet(), false, false) diff --git a/vendor/github.com/antlr4-go/antlr/v4/mutex.go b/vendor/github.com/antlr4-go/antlr/v4/mutex.go new file mode 100644 index 0000000000000..2b0cda4745ac0 --- /dev/null +++ b/vendor/github.com/antlr4-go/antlr/v4/mutex.go @@ -0,0 +1,41 @@ +//go:build !antlr.nomutex +// +build !antlr.nomutex + +package antlr + +import "sync" + +// Mutex is a simple mutex implementation which just delegates to sync.Mutex, it +// is used to provide a mutex implementation for the antlr package, which users +// can turn off with the build tag -tags antlr.nomutex +type Mutex struct { + mu sync.Mutex +} + +func (m *Mutex) Lock() { + m.mu.Lock() +} + +func (m *Mutex) Unlock() { + m.mu.Unlock() +} + +type RWMutex struct { + mu sync.RWMutex +} + +func (m *RWMutex) Lock() { + m.mu.Lock() +} + +func (m *RWMutex) Unlock() { + m.mu.Unlock() +} + +func (m *RWMutex) RLock() { + m.mu.RLock() +} + +func (m *RWMutex) RUnlock() { + m.mu.RUnlock() +} diff --git a/vendor/github.com/antlr4-go/antlr/v4/mutex_nomutex.go b/vendor/github.com/antlr4-go/antlr/v4/mutex_nomutex.go new file mode 100644 index 0000000000000..35ce4353ee21f --- /dev/null +++ b/vendor/github.com/antlr4-go/antlr/v4/mutex_nomutex.go @@ -0,0 +1,32 @@ +//go:build antlr.nomutex +// +build antlr.nomutex + +package antlr + +type Mutex struct{} + +func (m *Mutex) Lock() { + // No-op +} + +func (m *Mutex) Unlock() { + // No-op +} + +type RWMutex struct{} + +func (m *RWMutex) Lock() { + // No-op +} + +func (m *RWMutex) Unlock() { + // No-op +} + +func (m *RWMutex) RLock() { + // No-op +} + +func (m *RWMutex) RUnlock() { + // No-op +} diff --git a/vendor/github.com/antlr4-go/antlr/v4/parser_atn_simulator.go b/vendor/github.com/antlr4-go/antlr/v4/parser_atn_simulator.go index ae2869692a672..724fa17a19943 100644 --- a/vendor/github.com/antlr4-go/antlr/v4/parser_atn_simulator.go +++ b/vendor/github.com/antlr4-go/antlr/v4/parser_atn_simulator.go @@ -10,8 +10,6 @@ import ( "strings" ) -var () - // ClosureBusy is a store of ATNConfigs and is a tiny abstraction layer over // a standard JStore so that we can use Lazy instantiation of the JStore, mostly // to avoid polluting the stats module with a ton of JStore instances with nothing in them. @@ -883,7 +881,7 @@ func (p *ParserATNSimulator) getPredicatePredictions(ambigAlts *BitSet, altToPre // the ERROR state was reached, outerContext as the initial parser context from the paper // or the parser stack at the instant before prediction commences. // -// Teh func returns the value to return from [AdaptivePredict], or +// The func returns the value to return from [AdaptivePredict], or // [ATNInvalidAltNumber] if a suitable alternative was not // identified and [AdaptivePredict] should report an error instead. func (p *ParserATNSimulator) getSynValidOrSemInvalidAltThatFinishedDecisionEntryRule(configs *ATNConfigSet, outerContext ParserRuleContext) int { diff --git a/vendor/github.com/antlr4-go/antlr/v4/prediction_context.go b/vendor/github.com/antlr4-go/antlr/v4/prediction_context.go index c1b80cc1f0fd9..a1d5186b8f111 100644 --- a/vendor/github.com/antlr4-go/antlr/v4/prediction_context.go +++ b/vendor/github.com/antlr4-go/antlr/v4/prediction_context.go @@ -6,7 +6,6 @@ package antlr import ( "fmt" - "golang.org/x/exp/slices" "strconv" ) @@ -101,7 +100,7 @@ func NewArrayPredictionContext(parents []*PredictionContext, returnStates []int) hash = murmurUpdate(hash, returnState) } hash = murmurFinish(hash, len(parents)<<1) - + nec := &PredictionContext{} nec.cachedHash = hash nec.pcType = PredictionContextArray @@ -115,6 +114,9 @@ func (p *PredictionContext) Hash() int { } func (p *PredictionContext) Equals(other Collectable[*PredictionContext]) bool { + if p == other { + return true + } switch p.pcType { case PredictionContextEmpty: otherP := other.(*PredictionContext) @@ -138,13 +140,11 @@ func (p *PredictionContext) ArrayEquals(o Collectable[*PredictionContext]) bool if p.cachedHash != other.Hash() { return false // can't be same if hash is different } - + // Must compare the actual array elements and not just the array address // - return slices.Equal(p.returnStates, other.returnStates) && - slices.EqualFunc(p.parents, other.parents, func(x, y *PredictionContext) bool { - return x.Equals(y) - }) + return intSlicesEqual(p.returnStates, other.returnStates) && + pcSliceEqual(p.parents, other.parents) } func (p *PredictionContext) SingletonEquals(other Collectable[*PredictionContext]) bool { @@ -152,23 +152,23 @@ func (p *PredictionContext) SingletonEquals(other Collectable[*PredictionContext return false } otherP := other.(*PredictionContext) - if otherP == nil { + if otherP == nil || otherP.pcType != PredictionContextSingleton { return false } - + if p.cachedHash != otherP.Hash() { return false // Can't be same if hash is different } - + if p.returnState != otherP.getReturnState(0) { return false } - + // Both parents must be nil if one is if p.parentCtx == nil { return otherP.parentCtx == nil } - + return p.parentCtx.Equals(otherP.parentCtx) } @@ -225,27 +225,27 @@ func (p *PredictionContext) String() string { return "$" case PredictionContextSingleton: var up string - + if p.parentCtx == nil { up = "" } else { up = p.parentCtx.String() } - + if len(up) == 0 { if p.returnState == BasePredictionContextEmptyReturnState { return "$" } - + return strconv.Itoa(p.returnState) } - + return strconv.Itoa(p.returnState) + " " + up case PredictionContextArray: if p.isEmpty() { return "[]" } - + s := "[" for i := 0; i < len(p.returnStates); i++ { if i > 0 { @@ -263,7 +263,7 @@ func (p *PredictionContext) String() string { } } return s + "]" - + default: return "unknown" } @@ -309,18 +309,18 @@ func predictionContextFromRuleContext(a *ATN, outerContext RuleContext) *Predict parent := predictionContextFromRuleContext(a, outerContext.GetParent().(RuleContext)) state := a.states[outerContext.GetInvokingState()] transition := state.GetTransitions()[0] - + return SingletonBasePredictionContextCreate(parent, transition.(*RuleTransition).followState.GetStateNumber()) } func merge(a, b *PredictionContext, rootIsWildcard bool, mergeCache *JPCMap) *PredictionContext { - + // Share same graph if both same // if a == b || a.Equals(b) { return a } - + if a.pcType == PredictionContextSingleton && b.pcType == PredictionContextSingleton { return mergeSingletons(a, b, rootIsWildcard, mergeCache) } @@ -334,7 +334,7 @@ func merge(a, b *PredictionContext, rootIsWildcard bool, mergeCache *JPCMap) *Pr return b } } - + // Convert either Singleton or Empty to arrays, so that we can merge them // ara := convertToArray(a) @@ -395,7 +395,7 @@ func mergeSingletons(a, b *PredictionContext, rootIsWildcard bool, mergeCache *J return previous } } - + rootMerge := mergeRoot(a, b, rootIsWildcard) if rootMerge != nil { if mergeCache != nil { @@ -564,7 +564,7 @@ func mergeArrays(a, b *PredictionContext, rootIsWildcard bool, mergeCache *JPCMa i := 0 // walks a j := 0 // walks b k := 0 // walks target M array - + mergedReturnStates := make([]int, len(a.returnStates)+len(b.returnStates)) mergedParents := make([]*PredictionContext, len(a.returnStates)+len(b.returnStates)) // walk and merge to yield mergedParents, mergedReturnStates @@ -626,9 +626,9 @@ func mergeArrays(a, b *PredictionContext, rootIsWildcard bool, mergeCache *JPCMa mergedParents = mergedParents[0:k] mergedReturnStates = mergedReturnStates[0:k] } - + M := NewArrayPredictionContext(mergedParents, mergedReturnStates) - + // if we created same array as a or b, return that instead // TODO: JI track whether this is possible above during merge sort for speed and possibly avoid an allocation if M.Equals(a) { @@ -650,7 +650,7 @@ func mergeArrays(a, b *PredictionContext, rootIsWildcard bool, mergeCache *JPCMa return b } combineCommonParents(&mergedParents) - + if mergeCache != nil { mergeCache.Put(a, b, M) } @@ -666,7 +666,7 @@ func mergeArrays(a, b *PredictionContext, rootIsWildcard bool, mergeCache *JPCMa //goland:noinspection GoUnusedFunction func combineCommonParents(parents *[]*PredictionContext) { uniqueParents := NewJStore[*PredictionContext, Comparator[*PredictionContext]](pContextEqInst, PredictionContextCollection, "combineCommonParents for PredictionContext") - + for p := 0; p < len(*parents); p++ { parent := (*parents)[p] _, _ = uniqueParents.Put(parent) @@ -685,7 +685,7 @@ func getCachedBasePredictionContext(context *PredictionContext, contextCache *Pr if present { return existing } - + existing, present = contextCache.Get(context) if present { visited.Put(context, existing) @@ -722,6 +722,6 @@ func getCachedBasePredictionContext(context *PredictionContext, contextCache *Pr contextCache.add(updated) visited.Put(updated, updated) visited.Put(context, updated) - + return updated } diff --git a/vendor/github.com/antlr4-go/antlr/v4/recognizer.go b/vendor/github.com/antlr4-go/antlr/v4/recognizer.go index 2e0b504fb3db9..dcb8548cd1751 100644 --- a/vendor/github.com/antlr4-go/antlr/v4/recognizer.go +++ b/vendor/github.com/antlr4-go/antlr/v4/recognizer.go @@ -56,7 +56,7 @@ var tokenTypeMapCache = make(map[string]int) var ruleIndexMapCache = make(map[string]int) func (b *BaseRecognizer) checkVersion(toolVersion string) { - runtimeVersion := "4.12.0" + runtimeVersion := "4.13.1" if runtimeVersion != toolVersion { fmt.Println("ANTLR runtime and generated code versions disagree: " + runtimeVersion + "!=" + toolVersion) } diff --git a/vendor/github.com/antlr4-go/antlr/v4/statistics.go b/vendor/github.com/antlr4-go/antlr/v4/statistics.go index 70c0673a0f6b3..8cb5f3ed6f63a 100644 --- a/vendor/github.com/antlr4-go/antlr/v4/statistics.go +++ b/vendor/github.com/antlr4-go/antlr/v4/statistics.go @@ -9,7 +9,6 @@ import ( "path/filepath" "sort" "strconv" - "sync" ) // This file allows the user to collect statistics about the runtime of the ANTLR runtime. It is not enabled by default @@ -30,7 +29,7 @@ type goRunStats struct { // within this package. // jStats []*JStatRec - jStatsLock sync.RWMutex + jStatsLock RWMutex topN int topNByMax []*JStatRec topNByUsed []*JStatRec diff --git a/vendor/github.com/antlr4-go/antlr/v4/token.go b/vendor/github.com/antlr4-go/antlr/v4/token.go index 9670efb829e43..f5bc34229df13 100644 --- a/vendor/github.com/antlr4-go/antlr/v4/token.go +++ b/vendor/github.com/antlr4-go/antlr/v4/token.go @@ -104,6 +104,25 @@ func (b *BaseToken) GetSource() *TokenSourceCharStreamPair { return b.source } +func (b *BaseToken) GetText() string { + if b.text != "" { + return b.text + } + input := b.GetInputStream() + if input == nil { + return "" + } + n := input.Size() + if b.GetStart() < n && b.GetStop() < n { + return input.GetTextFromInterval(NewInterval(b.GetStart(), b.GetStop())) + } + return "" +} + +func (b *BaseToken) SetText(text string) { + b.text = text +} + func (b *BaseToken) GetTokenIndex() int { return b.tokenIndex } @@ -120,6 +139,28 @@ func (b *BaseToken) GetInputStream() CharStream { return b.source.charStream } +func (b *BaseToken) String() string { + txt := b.GetText() + if txt != "" { + txt = strings.Replace(txt, "\n", "\\n", -1) + txt = strings.Replace(txt, "\r", "\\r", -1) + txt = strings.Replace(txt, "\t", "\\t", -1) + } else { + txt = "" + } + + var ch string + if b.GetChannel() > 0 { + ch = ",channel=" + strconv.Itoa(b.GetChannel()) + } else { + ch = "" + } + + return "[@" + strconv.Itoa(b.GetTokenIndex()) + "," + strconv.Itoa(b.GetStart()) + ":" + strconv.Itoa(b.GetStop()) + "='" + + txt + "',<" + strconv.Itoa(b.GetTokenType()) + ">" + + ch + "," + strconv.Itoa(b.GetLine()) + ":" + strconv.Itoa(b.GetColumn()) + "]" +} + type CommonToken struct { BaseToken } @@ -170,44 +211,3 @@ func (c *CommonToken) clone() *CommonToken { t.text = c.GetText() return t } - -func (c *CommonToken) GetText() string { - if c.text != "" { - return c.text - } - input := c.GetInputStream() - if input == nil { - return "" - } - n := input.Size() - if c.start < n && c.stop < n { - return input.GetTextFromInterval(NewInterval(c.start, c.stop)) - } - return "" -} - -func (c *CommonToken) SetText(text string) { - c.text = text -} - -func (c *CommonToken) String() string { - txt := c.GetText() - if txt != "" { - txt = strings.Replace(txt, "\n", "\\n", -1) - txt = strings.Replace(txt, "\r", "\\r", -1) - txt = strings.Replace(txt, "\t", "\\t", -1) - } else { - txt = "" - } - - var ch string - if c.channel > 0 { - ch = ",channel=" + strconv.Itoa(c.channel) - } else { - ch = "" - } - - return "[@" + strconv.Itoa(c.tokenIndex) + "," + strconv.Itoa(c.start) + ":" + strconv.Itoa(c.stop) + "='" + - txt + "',<" + strconv.Itoa(c.tokenType) + ">" + - ch + "," + strconv.Itoa(c.line) + ":" + strconv.Itoa(c.column) + "]" -} diff --git a/vendor/github.com/antlr4-go/antlr/v4/utils.go b/vendor/github.com/antlr4-go/antlr/v4/utils.go index 733d7df9dc746..36a37f247acb1 100644 --- a/vendor/github.com/antlr4-go/antlr/v4/utils.go +++ b/vendor/github.com/antlr4-go/antlr/v4/utils.go @@ -326,3 +326,56 @@ func isDirectory(dir string) (bool, error) { } return fileInfo.IsDir(), err } + +// intSlicesEqual returns true if the two slices of ints are equal, and is a little +// faster than slices.Equal. +func intSlicesEqual(s1, s2 []int) bool { + if s1 == nil && s2 == nil { + return true + } + if s1 == nil || s2 == nil { + return false + } + if len(s1) == 0 && len(s2) == 0 { + return true + } + + if len(s1) == 0 || len(s2) == 0 || len(s1) != len(s2) { + return false + } + // If the slices are using the same memory, then they are the same slice + if &s1[0] == &s2[0] { + return true + } + for i, v := range s1 { + if v != s2[i] { + return false + } + } + return true +} + +func pcSliceEqual(s1, s2 []*PredictionContext) bool { + if s1 == nil && s2 == nil { + return true + } + if s1 == nil || s2 == nil { + return false + } + if len(s1) == 0 && len(s2) == 0 { + return true + } + if len(s1) == 0 || len(s2) == 0 || len(s1) != len(s2) { + return false + } + // If the slices are using the same memory, then they are the same slice + if &s1[0] == &s2[0] { + return true + } + for i, v := range s1 { + if !v.Equals(s2[i]) { + return false + } + } + return true +} diff --git a/vendor/github.com/google/cel-go/cel/BUILD.bazel b/vendor/github.com/google/cel-go/cel/BUILD.bazel index 81549fb4c5aac..62a56036a0010 100644 --- a/vendor/github.com/google/cel-go/cel/BUILD.bazel +++ b/vendor/github.com/google/cel-go/cel/BUILD.bazel @@ -10,25 +10,30 @@ go_library( "cel.go", "decls.go", "env.go", + "fieldpaths.go", "folding.go", - "io.go", "inlining.go", + "io.go", "library.go", "macro.go", "optimizer.go", "options.go", "program.go", + "prompt.go", "validator.go", ], + 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", "//common/ast:go_default_library", "//common/containers:go_default_library", "//common/decls:go_default_library", + "//common/env:go_default_library", "//common/functions:go_default_library", "//common/operators:go_default_library", "//common/overloads:go_default_library", @@ -40,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", @@ -60,10 +66,13 @@ go_test( "cel_test.go", "decls_test.go", "env_test.go", + "fieldpaths_test.go", "folding_test.go", - "io_test.go", "inlining_test.go", + "io_test.go", "optimizer_test.go", + "program_async_test.go", + "prompt_test.go", "validator_test.go", ], data = [ @@ -72,7 +81,12 @@ go_test( embed = [ ":go_default_library", ], + 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", @@ -82,10 +96,16 @@ 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//proto:go_default_library", "@org_golang_google_protobuf//encoding/prototext:go_default_library", + "@org_golang_google_protobuf//proto:go_default_library", "@org_golang_google_protobuf//types/known/structpb:go_default_library", "@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 0000000000000..85b28bcdb5e4d --- /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 0000000000000..a011114bdc8e5 --- /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 eb5a9f4cc520a..9ba957a7e31fd 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 418806021006b..c7c23fd51b5bd 100644 --- a/vendor/github.com/google/cel-go/cel/decls.go +++ b/vendor/github.com/google/cel-go/cel/decls.go @@ -142,8 +142,23 @@ func Constant(name string, t *Type, v ref.Val) EnvOption { // Variable creates an instance of a variable declaration with a variable name and type. func Variable(name string, t *Type) EnvOption { + return VariableWithDoc(name, t, "") +} + +// VariableWithDoc creates an instance of a variable declaration with a variable name, type, and doc string. +func VariableWithDoc(name string, t *Type, doc string) EnvOption { + return func(e *Env) (*Env, error) { + e.variables = append(e.variables, decls.NewVariableWithDoc(name, t, doc)) + return e, nil + } +} + +// VariableDecls configures a set of fully defined cel.VariableDecl instances in the environment. +func VariableDecls(vars ...*decls.VariableDecl) EnvOption { return func(e *Env) (*Env, error) { - e.variables = append(e.variables, decls.NewVariable(name, t)) + for _, v := range vars { + e.variables = append(e.variables, v) + } return e, nil } } @@ -183,13 +198,38 @@ func Function(name string, opts ...FunctionOpt) EnvOption { if err != nil { return nil, err } - if existing, found := e.functions[fn.Name()]; found { - fn, err = existing.Merge(fn) - if err != nil { - return nil, err + return FunctionDecls(fn)(e) + } +} + +// OverloadSelector selects an overload associated with a given function when it returns true. +// +// Used in combination with the FunctionDecl.Subset method. +type OverloadSelector = decls.OverloadSelector + +// IncludeOverloads defines an OverloadSelector which allow-lists a set of overloads by their ids. +func IncludeOverloads(overloadIDs ...string) OverloadSelector { + return decls.IncludeOverloads(overloadIDs...) +} + +// ExcludeOverloads defines an OverloadSelector which deny-lists a set of overloads by their ids. +func ExcludeOverloads(overloadIDs ...string) OverloadSelector { + return decls.ExcludeOverloads(overloadIDs...) +} + +// 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) { + var err error + for _, fn := range funcs { + if existing, found := e.functions[fn.Name()]; found { + fn, err = existing.Merge(fn) + if err != nil { + return nil, err + } } + e.functions[fn.Name()] = fn } - e.functions[fn.Name()] = fn return e, nil } } @@ -197,6 +237,13 @@ func Function(name string, opts ...FunctionOpt) EnvOption { // FunctionOpt defines a functional option for configuring a function declaration. type FunctionOpt = decls.FunctionOpt +// FunctionDocs provides a general usage documentation for the function. +// +// Use OverloadExamples to provide example usage instructions for specific overloads. +func FunctionDocs(docs ...string) FunctionOpt { + return decls.FunctionDocs(docs...) +} + // SingletonUnaryBinding creates a singleton function definition to be used for all function overloads. // // Note, this approach works well if operand is expected to have a specific trait which it implements, @@ -270,6 +317,11 @@ func MemberOverload(overloadID string, args []*Type, resultType *Type, opts ...O // OverloadOpt is a functional option for configuring a function overload. type OverloadOpt = decls.OverloadOpt +// OverloadExamples configures an example of how to invoke the overload. +func OverloadExamples(docs ...string) OverloadOpt { + return decls.OverloadExamples(docs...) +} + // UnaryBinding provides the implementation of a unary overload. The provided function is protected by a runtime // type-guard which ensures runtime type agreement between the overload signature and runtime argument types. func UnaryBinding(binding functions.UnaryOp) OverloadOpt { @@ -288,6 +340,38 @@ func FunctionBinding(binding functions.FunctionOp) OverloadOpt { return decls.FunctionBinding(binding) } +// 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 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 3bfe428992f90..784790ba2cdf5 100644 --- a/vendor/github.com/google/cel-go/cel/env.go +++ b/vendor/github.com/google/cel-go/cel/env.go @@ -16,6 +16,10 @@ package cel import ( "errors" + "fmt" + "math" + "slices" + "strings" "sync" "github.com/google/cel-go/checker" @@ -24,12 +28,16 @@ import ( celast "github.com/google/cel-go/common/ast" "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" "github.com/google/cel-go/interpreter" "github.com/google/cel-go/parser" exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1" + "google.golang.org/protobuf/reflect/protoreflect" ) // Source interface representing a user-provided expression. @@ -40,6 +48,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. @@ -127,15 +139,20 @@ type Env struct { Container *containers.Container variables []*decls.VariableDecl functions map[string]*decls.FunctionDecl - macros []parser.Macro + macros []Macro + contextProto protoreflect.MessageDescriptor adapter types.Adapter provider types.Provider features map[int]bool appliedFeatures map[int]bool - libraries map[string]bool + limits map[limitID]int + libraries map[string]SingletonLibrary validators []ASTValidator costOptions []checker.CostOption + funcBindOnce sync.Once + functionBindings []*functions.Overload + // Internal parser representation prsr *parser.Parser prsrOpts []parser.Option @@ -151,6 +168,173 @@ type Env struct { progOpts []ProgramOption } +// ToConfig produces a YAML-serializable env.Config object from the given environment. +// +// The serialized configuration value is intended to represent a baseline set of config +// options which could be used as input to an EnvOption to configure the majority of the +// environment from a file. +// +// Note: validators, features, flags, and safe-guard settings are not yet supported by +// the serialize method. Since optimizers are a separate construct from the environment +// and the standard expression components (parse, check, evalute), they are also not +// supported by the serialize method. +func (e *Env) ToConfig(name string) (*env.Config, error) { + conf := env.NewConfig(name) + // Container settings + if e.Container != containers.DefaultContainer { + conf.SetContainer(e.Container.Name()) + } + for _, typeName := range e.Container.AliasSet() { + 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 + // then diff the library version against the configured function + // to detect incremental overloads or rewrites. + libEnv, _ := NewCustomEnv() + libEnv, _ = Lib(lib)(libEnv) + for fnName, fnDecl := range libEnv.Functions() { + if len(fnDecl.OverloadDecls()) == 0 { + continue + } + overloads, exist := libOverloads[fnName] + if !exist { + overloads = make([]string, 0, len(fnDecl.OverloadDecls())) + } + for _, o := range fnDecl.OverloadDecls() { + overloads = append(overloads, o.ID()) + } + libOverloads[fnName] = overloads + } + subsetLib, canSubset := lib.(LibrarySubsetter) + alias := "" + if aliasLib, canAlias := lib.(LibraryAliaser); canAlias { + alias = aliasLib.LibraryAlias() + libName = alias + } + if libName == "stdlib" && canSubset { + conf.SetStdLib(subsetLib.LibrarySubset()) + continue + } + version := uint32(math.MaxUint32) + if versionLib, isVersioned := lib.(LibraryVersioner); isVersioned { + version = versionLib.LibraryVersion() + } + conf.AddExtensions(env.NewExtension(libName, version)) + } + + // If this is a custom environment without the standard env, mark the stdlib as disabled. + if conf.StdLib == nil && !e.HasLibrary("cel.lib.std") { + conf.SetStdLib(env.NewLibrarySubset().SetDisabled(true)) + } + + // Serialize the variables + vars := make([]*decls.VariableDecl, 0, len(e.Variables())) + stdTypeVars := map[string]*decls.VariableDecl{} + for _, v := range stdlib.Types() { + stdTypeVars[v.Name()] = v + } + for _, v := range e.Variables() { + if _, isStdType := stdTypeVars[v.Name()]; isStdType { + continue + } + vars = append(vars, v) + } + if e.contextProto != nil { + conf.SetContextVariable(env.NewContextVariable(string(e.contextProto.FullName()))) + skipVariables := map[string]bool{} + fields := e.contextProto.Fields() + for i := 0; i < fields.Len(); i++ { + field := fields.Get(i) + 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) + } + skipVariables[variable.Name()] = true + } + for _, v := range vars { + if _, found := skipVariables[v.Name()]; !found { + conf.AddVariableDecls(v) + } + } + } else { + conf.AddVariableDecls(vars...) + } + + // Serialize functions which are distinct from the ones configured by libraries. + for fnName, fnDecl := range e.Functions() { + if excludedOverloads, found := libOverloads[fnName]; found { + if newDecl := fnDecl.Subset(decls.ExcludeOverloads(excludedOverloads...)); newDecl != nil { + conf.AddFunctionDecls(newDecl) + } + } else { + conf.AddFunctionDecls(fnDecl) + } + } + + // Serialize validators + for _, val := range e.Validators() { + // Only add configurable validators to the env.Config as all others are + // expected to be implicitly enabled via extension libraries. + if confVal, ok := val.(ConfigurableASTValidator); ok { + conf.AddValidators(confVal.ToConfig()) + } + } + + 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.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 +} + // NewEnv creates a program environment configured with the standard library of CEL functions and // macros. The Env value returned can parse and check any CEL program which builds upon the core // features documented in the CEL specification. @@ -181,23 +365,25 @@ func NewEnv(opts ...EnvOption) (*Env, error) { // See the EnvOption helper functions for the options that can be used to configure the // environment. func NewCustomEnv(opts ...EnvOption) (*Env, error) { - registry, err := types.NewRegistry() + registry, err := types.NewProtoRegistry() if err != nil { return nil, err } return (&Env{ - variables: []*decls.VariableDecl{}, - functions: map[string]*decls.FunctionDecl{}, - macros: []parser.Macro{}, - Container: containers.DefaultContainer, - adapter: registry, - provider: registry, - features: map[int]bool{}, - appliedFeatures: map[int]bool{}, - libraries: map[string]bool{}, - validators: []ASTValidator{}, - progOpts: []ProgramOption{}, - costOptions: []checker.CostOption{}, + variables: []*decls.VariableDecl{}, + functions: map[string]*decls.FunctionDecl{}, + functionBindings: []*functions.Overload{}, + macros: []parser.Macro{}, + Container: containers.DefaultContainer, + adapter: registry, + provider: registry, + features: map[int]bool{}, + appliedFeatures: map[int]bool{}, + limits: map[limitID]int{}, + libraries: map[string]SingletonLibrary{}, + validators: []ASTValidator{}, + progOpts: []ProgramOption{}, + costOptions: []checker.CostOption{}, }).configure(opts) } @@ -213,6 +399,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 { @@ -254,6 +454,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. // @@ -263,7 +481,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 @@ -358,16 +580,21 @@ func (e *Env) Extend(opts ...EnvOption) (*Env, error) { for k, v := range e.appliedFeatures { appliedFeaturesCopy[k] = v } + limitsCopy := make(map[limitID]int, len(e.limits)) + for k, v := range e.limits { + limitsCopy[k] = v + } funcsCopy := make(map[string]*decls.FunctionDecl, len(e.functions)) for k, v := range e.functions { funcsCopy[k] = v } - libsCopy := make(map[string]bool, len(e.libraries)) + 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) @@ -376,9 +603,11 @@ func (e *Env) Extend(opts ...EnvOption) (*Env, error) { variables: varsCopy, functions: funcsCopy, macros: macsCopy, + contextProto: e.contextProto, progOpts: progOptsCopy, adapter: adapter, features: featuresCopy, + limits: limitsCopy, appliedFeatures: appliedFeaturesCopy, libraries: libsCopy, validators: validatorsCopy, @@ -399,8 +628,8 @@ func (e *Env) HasFeature(flag int) bool { // HasLibrary returns whether a specific SingletonLibrary has been configured in the environment. func (e *Env) HasLibrary(libName string) bool { - configured, exists := e.libraries[libName] - return exists && configured + _, exists := e.libraries[libName] + return exists } // Libraries returns a list of SingletonLibrary that have been configured in the environment. @@ -418,9 +647,27 @@ func (e *Env) HasFunction(functionName string) bool { return ok } -// Functions returns map of Functions, keyed by function name, that have been configured in the environment. +// 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 { - return e.functions + shallowCopy := make(map[string]*decls.FunctionDecl, len(e.functions)) + for nm, fn := range e.functions { + shallowCopy[nm] = fn + } + return shallowCopy +} + +// 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 +} + +// 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 } // HasValidator returns whether a specific ASTValidator has been configured in the environment. @@ -433,12 +680,20 @@ func (e *Env) HasValidator(name string) bool { return false } +// Validators returns the set of ASTValidators configured on the environment. +func (e *Env) Validators() []ASTValidator { + return e.validators[:] +} + // Parse parses the input expression value `txt` to a Ast and/or a set of Issues. // // 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) } @@ -459,6 +714,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...) } @@ -502,31 +763,30 @@ func (e *Env) TypeProvider() ref.TypeProvider { return &interopLegacyTypeProvider{Provider: e.provider} } -// UnknownVars returns an interpreter.PartialActivation which marks all variables declared in the -// Env as unknown AttributePattern values. +// UnknownVars returns a PartialActivation which marks all variables declared in the Env as +// unknown AttributePattern values. // -// Note, the UnknownVars will behave the same as an interpreter.EmptyActivation unless the -// PartialAttributes option is provided as a ProgramOption. -func (e *Env) UnknownVars() interpreter.PartialActivation { +// Note, the UnknownVars will behave the same as an cel.NoVars() unless the PartialAttributes +// option is provided as a ProgramOption. +func (e *Env) UnknownVars() PartialActivation { act := interpreter.EmptyActivation() part, _ := PartialVars(act, e.computeUnknownVars(act)...) return part } -// PartialVars returns an interpreter.PartialActivation where all variables not in the input variable +// PartialVars returns a PartialActivation where all variables not in the input variable // set, but which have been configured in the environment, are marked as unknown. // -// The `vars` value may either be an interpreter.Activation or any valid input to the -// interpreter.NewActivation call. +// The `vars` value may either be an Activation or any valid input to the cel.NewActivation call. // // Note, this is equivalent to calling cel.PartialVars and manually configuring the set of unknown // variables. For more advanced use cases of partial state where portions of an object graph, rather // than top-level variables, are missing the PartialVars() method may be a more suitable choice. // -// Note, the PartialVars will behave the same as an interpreter.EmptyActivation unless the -// PartialAttributes option is provided as a ProgramOption. -func (e *Env) PartialVars(vars any) (interpreter.PartialActivation, error) { - act, err := interpreter.NewActivation(vars) +// Note, the PartialVars will behave the same as cel.NoVars() unless the PartialAttributes +// option is provided as a ProgramOption. +func (e *Env) PartialVars(vars any) (PartialActivation, error) { + act, err := NewActivation(vars) if err != nil { return nil, err } @@ -598,10 +858,15 @@ func (e *Env) configure(opts []EnvOption) (*Env, error) { } } - // If the default UTC timezone fix has been enabled, make sure the library is configured - e, err = e.maybeApplyFeature(featureDefaultUTCTimeZone, Lib(timeUTCLibrary{})) - if err != nil { - return nil, err + // 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.appliedFeatures[featureDefaultUTCTimeZone] = true + e, err = Lib(timeLegacyLibrary{})(e) + if err != nil { + return nil, err + } + } } // Configure the parser. @@ -618,11 +883,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() @@ -641,6 +930,8 @@ func (e *Env) initChecker() (*checker.Env, error) { chkOpts = append(chkOpts, checker.CrossTypeNumericComparisons( e.HasFeature(featureCrossTypeNumericComparisons))) + chkOpts = append(chkOpts, + checker.JSONFieldNames(e.HasFeature(featureJSONFieldNames))) ce, err := checker.NewEnv(e.Container, e.provider, chkOpts...) if err != nil { @@ -685,30 +976,9 @@ func (e *Env) getCheckerOrError() (*checker.Env, error) { return e.chk, e.chkErr } -// maybeApplyFeature determines whether the feature-guarded option is enabled, and if so applies -// the feature if it has not already been enabled. -func (e *Env) maybeApplyFeature(feature int, option EnvOption) (*Env, error) { - if !e.HasFeature(feature) { - return e, nil - } - _, applied := e.appliedFeatures[feature] - if applied { - return e, nil - } - e, err := option(e) - if err != nil { - return nil, err - } - // record that the feature has been applied since it will generate declarations - // and functions which will be propagated on Extend() calls and which should only - // be registered once. - e.appliedFeatures[feature] = true - return e, nil -} - // computeUnknownVars determines a set of missing variables based on the input activation and the // environment's configured declaration set. -func (e *Env) computeUnknownVars(vars interpreter.Activation) []*interpreter.AttributePattern { +func (e *Env) computeUnknownVars(vars Activation) []*interpreter.AttributePattern { var unknownPatterns []*interpreter.AttributePattern for _, v := range e.variables { varName := v.Name() @@ -731,6 +1001,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) @@ -839,9 +1119,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 0000000000000..570fce3a465b0 --- /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 d7060896d37ec..5525f0805e1dc 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,6 +40,23 @@ func MaxConstantFoldIterations(limit int) ConstantFoldingOption { } } +// 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. +// +// Defaults to the NoVars() Activation +func FoldKnownValues(knownValues Activation) ConstantFoldingOption { + return func(opt *constantFoldingOptimizer) (*constantFoldingOptimizer, error) { + if knownValues != nil { + opt.knownValues = knownValues + } else { + opt.knownValues = NoVars() + } + return opt, nil + } +} + // NewConstantFoldingOptimizer creates an optimizer which inlines constant scalar an aggregate // literal values within function calls and select statements with their evaluated result. func NewConstantFoldingOptimizer(opts ...ConstantFoldingOption) (ASTOptimizer, error) { @@ -56,6 +75,7 @@ func NewConstantFoldingOptimizer(opts ...ConstantFoldingOption) (ASTOptimizer, e type constantFoldingOptimizer struct { maxFoldIterations int + knownValues Activation } // Optimize queries the expression graph for scalar and aggregate literal expressions within call and @@ -68,30 +88,37 @@ func (opt *constantFoldingOptimizer) Optimize(ctx *OptimizerContext, a *ast.AST) // Walk the list of foldable expression and continue to fold until there are no more folds left. // All of the fold candidates returned by the constantExprMatcher should succeed unless there's // a logic bug with the selection of expressions. - foldableExprs := ast.MatchDescendants(root, constantExprMatcher) + constantExprMatcherCapture := func(e ast.NavigableExpr) bool { return opt.constantExprMatcher(ctx, a, e) } + foldableExprs := ast.MatchDescendants(root, constantExprMatcherCapture) foldCount := 0 for len(foldableExprs) != 0 && foldCount < opt.maxFoldIterations { 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, fold) { continue } // Otherwise, assume all context is needed to evaluate the expression. - err := tryFold(ctx, a, fold) - if err != nil { + err := opt.tryFold(ctx, a, fold) + // 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 && !errors.Is(err, errCannotFold) { ctx.ReportErrorAtID(fold.ID(), "constant-folding evaluation failed: %v", err.Error()) return a } } foldCount++ - foldableExprs = ast.MatchDescendants(root, constantExprMatcher) + foldableExprs = ast.MatchDescendants(root, constantExprMatcherCapture) } // Once all of the constants have been folded, try to run through the remaining comprehensions // one last time. In this case, there's no guarantee they'll run, so we only update the // target comprehension node with the literal value if the evaluation succeeds. for _, compre := range ast.MatchDescendants(root, ast.KindMatcher(ast.ComprehensionKind)) { - tryFold(ctx, a, compre) + opt.tryFold(ctx, a, compre) } // If the output is a list, map, or struct which contains optional entries, then prune it @@ -117,20 +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 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 +func (opt *constantFoldingOptimizer) tryFold(ctx *OptimizerContext, a *ast.AST, expr ast.Expr) error { + activation := opt.knownValues + if activation == nil { + activation = NoVars() } - out, _, err := prg.Eval(NoVars()) + navExpr := expr.(ast.NavigableExpr) + out, err := evaluateExpr(ctx, a, navExpr, activation) if err != nil { return err } @@ -139,16 +165,49 @@ func tryFold(ctx *OptimizerContext, a *ast.AST, expr ast.Expr) error { return nil } +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, err + } + // 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 { + return false + } + return function.HasLateBinding() +} + // maybePruneBranches inspects the non-strict call expression to determine whether // 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] @@ -169,11 +228,17 @@ 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 } @@ -183,7 +248,7 @@ func maybePruneBranches(ctx *OptimizerContext, expr ast.NavigableExpr) bool { 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 { @@ -206,10 +271,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 } @@ -217,6 +286,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. // @@ -247,9 +326,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 } @@ -455,13 +534,15 @@ func adaptLiteral(ctx *OptimizerContext, val ref.Val) (ast.Expr, error) { // Only comprehensions which are not nested are included as possible constant folds, and only // if all variables referenced in the comprehension stack exist are only iteration or // accumulation variables. -func constantExprMatcher(e ast.NavigableExpr) bool { +func (opt *constantFoldingOptimizer) constantExprMatcher(ctx *OptimizerContext, a *ast.AST, e ast.NavigableExpr) bool { switch e.Kind() { case ast.CallKind: return constantCallMatcher(e) case ast.SelectKind: 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 && !hasComprehensionVar(e) case ast.ComprehensionKind: if isNestedComprehension(e) { return false @@ -473,10 +554,17 @@ func constantExprMatcher(e ast.NavigableExpr) bool { 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, e) { + constantExprs = false + } }) ast.PreOrderVisit(e, visitor) return constantExprs @@ -510,17 +598,33 @@ 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 } } @@ -535,6 +639,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 a4530e19e7e65..d9a5e89a5f5b0 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 a327c9672ddce..c991c95c3ff02 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. @@ -99,7 +123,13 @@ func AstToParsedExpr(a *Ast) (*exprpb.ParsedExpr, error) { // Note, the conversion may not be an exact replica of the original expression, but will produce // a string that is semantically equivalent and whose textual representation is stable. func AstToString(a *Ast) (string, error) { - return parser.Unparse(a.NativeRep().Expr(), a.NativeRep().SourceInfo()) + return ExprToString(a.NativeRep().Expr(), a.NativeRep().SourceInfo()) +} + +// ExprToString converts an AST Expr node back to a string using macro call tracking metadata from +// source info if any macros are encountered within the expression. +func ExprToString(e ast.Expr, info *ast.SourceInfo) (string, error) { + return parser.Unparse(e, info) } // RefValueToValue converts between ref.Val and google.api.expr.v1alpha1.Value. @@ -120,6 +150,55 @@ func ValueAsAlphaProto(res ref.Val) (*exprpb.Value, error) { return alpha, err } +// RefValToExprValue converts between ref.Val and google.api.expr.v1alpha1.ExprValue. +// The result ExprValue is the serialized proto form. +func RefValToExprValue(res ref.Val) (*exprpb.ExprValue, error) { + return ExprValueAsAlphaProto(res) +} + +// ExprValueAsAlphaProto converts between ref.Val and google.api.expr.v1alpha1.ExprValue. +// The result ExprValue is the serialized proto form. +func ExprValueAsAlphaProto(res ref.Val) (*exprpb.ExprValue, error) { + canonical, err := ExprValueAsProto(res) + if err != nil { + return nil, err + } + alpha := &exprpb.ExprValue{} + err = convertProto(canonical, alpha) + return alpha, err +} + +// ExprValueAsProto converts between ref.Val and cel.expr.ExprValue. +// The result ExprValue is the serialized proto form. +func ExprValueAsProto(res ref.Val) (*celpb.ExprValue, error) { + switch res := res.(type) { + case *types.Unknown: + return &celpb.ExprValue{ + Kind: &celpb.ExprValue_Unknown{ + Unknown: &celpb.UnknownSet{ + Exprs: res.IDs(), + }, + }}, nil + case *types.Err: + return &celpb.ExprValue{ + Kind: &celpb.ExprValue_Error{ + Error: &celpb.ErrorSet{ + // Keeping the error code as UNKNOWN since there's no error codes associated with + // Cel-Go runtime errors. + Errors: []*celpb.Status{{Code: 2, Message: res.Error()}}, + }, + }, + }, nil + default: + val, err := ValueAsProto(res) + if err != nil { + return nil, err + } + return &celpb.ExprValue{ + Kind: &celpb.ExprValue_Value{Value: val}}, nil + } +} + // ValueAsProto converts between ref.Val and cel.expr.Value. // The result Value is the serialized proto form. The ref.Val must not be error or unknown. func ValueAsProto(res ref.Val) (*celpb.Value, error) { diff --git a/vendor/github.com/google/cel-go/cel/library.go b/vendor/github.com/google/cel-go/cel/library.go index c0aef5019095d..332eb3f17037a 100644 --- a/vendor/github.com/google/cel-go/cel/library.go +++ b/vendor/github.com/google/cel-go/cel/library.go @@ -17,11 +17,11 @@ package cel import ( "fmt" "math" - "strconv" - "strings" - "time" + "github.com/google/cel-go/common" "github.com/google/cel-go/common/ast" + "github.com/google/cel-go/common/decls" + "github.com/google/cel-go/common/env" "github.com/google/cel-go/common/operators" "github.com/google/cel-go/common/overloads" "github.com/google/cel-go/common/stdlib" @@ -71,6 +71,23 @@ type SingletonLibrary interface { LibraryName() string } +// LibraryAliaser generates a simple named alias for the library, for use during environment serialization. +type LibraryAliaser interface { + LibraryAlias() string +} + +// LibrarySubsetter provides the subset description associated with the library, nil if not subset. +type LibrarySubsetter interface { + LibrarySubset() *env.LibrarySubset +} + +// LibraryVersioner provides a version number for the library. +// +// If not implemented, the library version will be flagged as 'latest' during environment serialization. +type LibraryVersioner interface { + LibraryVersion() uint32 +} + // Lib creates an EnvOption out of a Library, allowing libraries to be provided as functional args, // and to be linked to each other. func Lib(l Library) EnvOption { @@ -80,7 +97,7 @@ func Lib(l Library) EnvOption { if e.HasLibrary(singleton.LibraryName()) { return e, nil } - e.libraries[singleton.LibraryName()] = true + e.libraries[singleton.LibraryName()] = singleton } var err error for _, opt := range l.CompileOptions() { @@ -94,26 +111,78 @@ func Lib(l Library) EnvOption { } } +// StdLibOption specifies a functional option for configuring the standard CEL library. +type StdLibOption func(*stdLibrary) *stdLibrary + +// StdLibSubset configures the standard library to use a subset of its functions and macros. +// +// Since the StdLib is a singleton library, only the first instance of the StdLib() environment options +// will be configured on the environment which means only the StdLibSubset() initially configured with +// the library will be used. +func StdLibSubset(subset *env.LibrarySubset) StdLibOption { + return func(lib *stdLibrary) *stdLibrary { + lib.subset = subset + return lib + } +} + // StdLib returns an EnvOption for the standard library of CEL functions and macros. -func StdLib() EnvOption { - return Lib(stdLibrary{}) +func StdLib(opts ...StdLibOption) EnvOption { + lib := &stdLibrary{} + for _, o := range opts { + lib = o(lib) + } + return Lib(lib) } // stdLibrary implements the Library interface and provides functional options for the core CEL // features documented in the specification. -type stdLibrary struct{} +type stdLibrary struct { + subset *env.LibrarySubset +} // LibraryName implements the SingletonLibrary interface method. -func (stdLibrary) LibraryName() string { +func (*stdLibrary) LibraryName() string { return "cel.lib.std" } +// LibraryAlias returns the simple name of the library. +func (*stdLibrary) LibraryAlias() string { + return "stdlib" +} + +// LibrarySubset returns the env.LibrarySubset definition associated with the CEL Library. +func (lib *stdLibrary) LibrarySubset() *env.LibrarySubset { + return lib.subset +} + // CompileOptions returns options for the standard CEL function declarations and macros. -func (stdLibrary) CompileOptions() []EnvOption { +func (lib *stdLibrary) CompileOptions() []EnvOption { + funcs := stdlib.Functions() + macros := StandardMacros + if lib.subset != nil { + subMacros := []Macro{} + for _, m := range macros { + if lib.subset.SubsetMacro(m.Function()) { + subMacros = append(subMacros, m) + } + } + macros = subMacros + subFuncs := []*decls.FunctionDecl{} + for _, fn := range funcs { + if f, include := lib.subset.SubsetFunction(fn); include { + subFuncs = append(subFuncs, f) + } + } + funcs = subFuncs + } return []EnvOption{ func(e *Env) (*Env, error) { var err error - for _, fn := range stdlib.Functions() { + if err = lib.subset.Validate(); err != nil { + return nil, err + } + for _, fn := range funcs { existing, found := e.functions[fn.Name()] if found { fn, err = existing.Merge(fn) @@ -125,16 +194,12 @@ func (stdLibrary) CompileOptions() []EnvOption { } return e, nil }, - func(e *Env) (*Env, error) { - e.variables = append(e.variables, stdlib.Types()...) - return e, nil - }, - Macros(StandardMacros...), + Macros(macros...), } } // ProgramOptions returns function implementations for the standard CEL functions. -func (stdLibrary) ProgramOptions() []ProgramOption { +func (*stdLibrary) ProgramOptions() []ProgramOption { return []ProgramOption{} } @@ -263,7 +328,7 @@ func (stdLibrary) ProgramOptions() []ProgramOption { // be expressed with `optMap`. // // msg.?elements.optFlatMap(e, e[?0]) // return the first element if present. - +// // # First // // Introduced in version: 2 @@ -272,7 +337,7 @@ func (stdLibrary) ProgramOptions() []ProgramOption { // optional.None. // // [1, 2, 3].first().value() == 1 - +// // # Last // // Introduced in version: 2 @@ -283,7 +348,7 @@ func (stdLibrary) ProgramOptions() []ProgramOption { // [1, 2, 3].last().value() == 3 // // This is syntactic sugar for msg.elements[msg.elements.size()-1]. - +// // # Unwrap / UnwrapOpt // // Introduced in version: 2 @@ -293,7 +358,6 @@ func (stdLibrary) ProgramOptions() []ProgramOption { // // optional.unwrap([optional.of(42), optional.none()]) == [42] // [optional.of(42), optional.none()].unwrapOpt() == [42] - func OptionalTypes(opts ...OptionalTypesOption) EnvOption { lib := &optionalLib{version: math.MaxUint32} for _, opt := range opts { @@ -326,10 +390,20 @@ func OptionalTypesVersion(version uint32) OptionalTypesOption { } // LibraryName implements the SingletonLibrary interface method. -func (lib *optionalLib) LibraryName() string { +func (*optionalLib) LibraryName() string { return "cel.lib.optional" } +// LibraryAlias returns the simple name of the library. +func (*optionalLib) LibraryAlias() string { + return "optional" +} + +// LibraryVersion returns the version of the library. +func (lib *optionalLib) LibraryVersion() uint32 { + return lib.version +} + // CompileOptions implements the Library interface method. func (lib *optionalLib) CompileOptions() []EnvOption { paramTypeK := TypeParamType("K") @@ -347,16 +421,29 @@ func (lib *optionalLib) CompileOptions() []EnvOption { Types(types.OptionalType), // Configure the optMap and optFlatMap macros. - Macros(ReceiverMacro(optMapMacro, 2, optMap)), + Macros(ReceiverMacro(optMapMacro, 2, optMap, + MacroDocs(`perform computation on the value if present and return the result as an optional`), + MacroExamples( + common.MultilineDescription( + `// sub with the prefix 'dev.cel' or optional.none()`, + `request.auth.tokens.?sub.optMap(id, 'dev.cel.' + id)`), + `optional.none().optMap(i, i * 2) // optional.none()`))), // Global and member functions for working with optional values. Function(optionalOfFunc, + FunctionDocs(`create a new optional_type(T) with a value where any value is considered valid`), Overload("optional_of", []*Type{paramTypeV}, optionalTypeV, + OverloadExamples(`optional.of(1) // optional(1)`), UnaryBinding(func(value ref.Val) ref.Val { return types.OptionalOf(value) }))), Function(optionalOfNonZeroValueFunc, + FunctionDocs(`create a new optional_type(T) with a value, if the value is not a zero or empty value`), Overload("optional_ofNonZeroValue", []*Type{paramTypeV}, optionalTypeV, + OverloadExamples( + `optional.ofNonZeroValue(null) // optional.none()`, + `optional.ofNonZeroValue("") // optional.none()`, + `optional.ofNonZeroValue("hello") // optional.of('hello')`), UnaryBinding(func(value ref.Val) ref.Val { v, isZeroer := value.(traits.Zeroer) if !isZeroer || !v.IsZeroValue() { @@ -365,18 +452,26 @@ func (lib *optionalLib) CompileOptions() []EnvOption { return types.OptionalNone }))), Function(optionalNoneFunc, + FunctionDocs(`singleton value representing an optional without a value`), Overload("optional_none", []*Type{}, optionalTypeV, + OverloadExamples(`optional.none()`), FunctionBinding(func(values ...ref.Val) ref.Val { return types.OptionalNone }))), Function(valueFunc, + FunctionDocs(`obtain the value contained by the optional, error if optional.none()`), MemberOverload("optional_value", []*Type{optionalTypeV}, paramTypeV, + OverloadExamples( + `optional.of(1).value() // 1`, + `optional.none().value() // error`), UnaryBinding(func(value ref.Val) ref.Val { opt := value.(*types.Optional) return opt.GetValue() }))), Function(hasValueFunc, + FunctionDocs(`determine whether the optional contains a value`), MemberOverload("optional_hasValue", []*Type{optionalTypeV}, BoolType, + OverloadExamples(`optional.of({1: 2}).hasValue() // true`), UnaryBinding(func(value ref.Val) ref.Val { opt := value.(*types.Optional) return types.Bool(opt.HasValue()) @@ -385,21 +480,43 @@ func (lib *optionalLib) CompileOptions() []EnvOption { // Implementation of 'or' and 'orValue' are special-cased to support short-circuiting in the // evaluation chain. Function("or", - MemberOverload("optional_or_optional", []*Type{optionalTypeV, optionalTypeV}, optionalTypeV)), + FunctionDocs(`chain optional expressions together, picking the first valued optional expression`), + MemberOverload("optional_or_optional", []*Type{optionalTypeV, optionalTypeV}, optionalTypeV, + OverloadExamples( + `optional.none().or(optional.of(1)) // optional.of(1)`, + common.MultilineDescription( + `// either a value from the first list, a value from the second, or optional.none()`, + `[1, 2, 3][?x].or([3, 4, 5][?y])`)))), Function("orValue", - MemberOverload("optional_orValue_value", []*Type{optionalTypeV, paramTypeV}, paramTypeV)), + FunctionDocs(`chain optional expressions together picking the first valued optional or the default value`), + MemberOverload("optional_orValue_value", []*Type{optionalTypeV, paramTypeV}, paramTypeV, + OverloadExamples( + common.MultilineDescription( + `// pick the value for the given key if the key exists, otherwise return 'you'`, + `{'hello': 'world', 'goodbye': 'cruel world'}[?greeting].orValue('you')`)))), // OptSelect is handled specially by the type-checker, so the receiver's field type is used to determine the // optput type. Function(operators.OptSelect, - Overload("select_optional_field", []*Type{DynType, StringType}, optionalTypeV)), + FunctionDocs(`if the field is present create an optional of the field value, otherwise return optional.none()`), + Overload("select_optional_field", []*Type{DynType, StringType}, optionalTypeV, + OverloadExamples( + `msg.?field // optional.of(field) if non-empty, otherwise optional.none()`, + `msg.?field.?nested_field // optional.of(nested_field) if both field and nested_field are non-empty.`))), // OptIndex is handled mostly like any other indexing operation on a list or map, so the type-checker can use // these signatures to determine type-agreement without any special handling. Function(operators.OptIndex, - Overload("list_optindex_optional_int", []*Type{listTypeV, IntType}, optionalTypeV), + FunctionDocs(`if the index is present create an optional of the field value, otherwise return optional.none()`), + Overload("list_optindex_optional_int", []*Type{listTypeV, IntType}, optionalTypeV, + OverloadExamples(`[1, 2, 3][?x] // element value if x is in the list size, else optional.none()`)), Overload("optional_list_optindex_optional_int", []*Type{OptionalType(listTypeV), IntType}, optionalTypeV), - Overload("map_optindex_optional_value", []*Type{mapTypeKV, paramTypeK}, optionalTypeV), + Overload("map_optindex_optional_value", []*Type{mapTypeKV, paramTypeK}, optionalTypeV, + OverloadExamples( + `map_value[?key] // value at the key if present, else optional.none()`, + common.MultilineDescription( + `// map key-value if index is a valid map key, else optional.none()`, + `{0: 2, 2: 4, 6: 8}[?index]`))), Overload("optional_map_optindex_optional_value", []*Type{OptionalType(mapTypeKV), paramTypeK}, optionalTypeV)), // Index overloads to accommodate using an optional value as the operand. @@ -408,45 +525,62 @@ func (lib *optionalLib) CompileOptions() []EnvOption { Overload("optional_map_index_value", []*Type{OptionalType(mapTypeKV), paramTypeK}, optionalTypeV)), } if lib.version >= 1 { - opts = append(opts, Macros(ReceiverMacro(optFlatMapMacro, 2, optFlatMap))) + opts = append(opts, Macros(ReceiverMacro(optFlatMapMacro, 2, optFlatMap, + MacroDocs(`perform computation on the value if present and produce an optional value within the computation`), + MacroExamples( + common.MultilineDescription( + `// m = {'key': {}}`, + `m.?key.optFlatMap(k, k.?subkey) // optional.none()`), + common.MultilineDescription( + `// m = {'key': {'subkey': 'value'}}`, + `m.?key.optFlatMap(k, k.?subkey) // optional.of('value')`), + )))) } if lib.version >= 2 { opts = append(opts, Function("last", + FunctionDocs(`return the last value in a list if present, otherwise optional.none()`), MemberOverload("list_last", []*Type{listTypeV}, optionalTypeV, + OverloadExamples( + `[].last() // optional.none()`, + `[1, 2, 3].last() ? optional.of(3)`), UnaryBinding(func(v ref.Val) ref.Val { list := v.(traits.Lister) - sz := list.Size().Value().(int64) - - if sz == 0 { + sz := list.Size().(types.Int) + if sz == types.IntZero { return types.OptionalNone } - return types.OptionalOf(list.Get(types.Int(sz - 1))) }), ), )) opts = append(opts, Function("first", + FunctionDocs(`return the first value in a list if present, otherwise optional.none()`), MemberOverload("list_first", []*Type{listTypeV}, optionalTypeV, + OverloadExamples( + `[].first() // optional.none()`, + `[1, 2, 3].first() ? optional.of(1)`), UnaryBinding(func(v ref.Val) ref.Val { list := v.(traits.Lister) - sz := list.Size().Value().(int64) - - if sz == 0 { + sz := list.Size().(types.Int) + if sz == types.IntZero { return types.OptionalNone } - return types.OptionalOf(list.Get(types.Int(0))) }), ), )) opts = append(opts, Function(optionalUnwrapFunc, + FunctionDocs(`convert a list of optional values to a list containing only value which are not optional.none()`), Overload("optional_unwrap", []*Type{listOptionalTypeV}, listTypeV, + OverloadExamples(`optional.unwrap([optional.of(1), optional.none()]) // [1]`), UnaryBinding(optUnwrap)))) opts = append(opts, Function(unwrapOptFunc, + FunctionDocs(`convert a list of optional values to a list containing only value which are not optional.none()`), MemberOverload("optional_unwrapOpt", []*Type{listOptionalTypeV}, listTypeV, + OverloadExamples(`[optional.of(1), optional.none()].unwrapOpt() // [1]`), UnaryBinding(optUnwrap)))) } @@ -456,10 +590,15 @@ func (lib *optionalLib) CompileOptions() []EnvOption { // ProgramOptions implements the Library interface method. func (lib *optionalLib) ProgramOptions() []ProgramOption { return []ProgramOption{ - CustomDecorator(decorateOptionalOr), + CustomDecoratorV2(decorateOptionalOr), } } +// Version returns the current version of the library. +func (lib *optionalLib) Version() uint32 { + return lib.version +} + func optMap(meh MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *Error) { varIdent := args[0] varName := "" @@ -544,7 +683,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 @@ -581,8 +720,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. @@ -590,27 +729,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. @@ -618,265 +764,122 @@ 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) } -type timeUTCLibrary struct{} +// 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{} -func (timeUTCLibrary) CompileOptions() []EnvOption { +func (timeLegacyLibrary) CompileOptions() []EnvOption { return timeOverloadDeclarations } -func (timeUTCLibrary) ProgramOptions() []ProgramOption { +func (timeLegacyLibrary) ProgramOptions() []ProgramOption { return []ProgramOption{} } // Declarations and functions which enable using UTC on time.Time inputs when the timezone is unspecified // in the CEL expression. var ( - utcTZ = types.String("UTC") - timeOverloadDeclarations = []EnvOption{ - Function(overloads.TimeGetHours, - MemberOverload(overloads.DurationToHours, []*Type{DurationType}, IntType, - UnaryBinding(types.DurationGetHours))), - Function(overloads.TimeGetMinutes, - MemberOverload(overloads.DurationToMinutes, []*Type{DurationType}, IntType, - UnaryBinding(types.DurationGetMinutes))), - Function(overloads.TimeGetSeconds, - MemberOverload(overloads.DurationToSeconds, []*Type{DurationType}, IntType, - UnaryBinding(types.DurationGetSeconds))), - Function(overloads.TimeGetMilliseconds, - MemberOverload(overloads.DurationToMilliseconds, []*Type{DurationType}, IntType, - UnaryBinding(types.DurationGetMilliseconds))), Function(overloads.TimeGetFullYear, MemberOverload(overloads.TimestampToYear, []*Type{TimestampType}, IntType, UnaryBinding(func(ts ref.Val) ref.Val { - return timestampGetFullYear(ts, utcTZ) + t := ts.(types.Timestamp) + return t.Receive(overloads.TimeGetFullYear, overloads.TimestampToYear, []ref.Val{}) }), ), - MemberOverload(overloads.TimestampToYearWithTz, []*Type{TimestampType, StringType}, IntType, - BinaryBinding(timestampGetFullYear), - ), ), Function(overloads.TimeGetMonth, MemberOverload(overloads.TimestampToMonth, []*Type{TimestampType}, IntType, UnaryBinding(func(ts ref.Val) ref.Val { - return timestampGetMonth(ts, utcTZ) + t := ts.(types.Timestamp) + return t.Receive(overloads.TimeGetMonth, overloads.TimestampToMonth, []ref.Val{}) }), ), - MemberOverload(overloads.TimestampToMonthWithTz, []*Type{TimestampType, StringType}, IntType, - BinaryBinding(timestampGetMonth), - ), ), Function(overloads.TimeGetDayOfYear, MemberOverload(overloads.TimestampToDayOfYear, []*Type{TimestampType}, IntType, UnaryBinding(func(ts ref.Val) ref.Val { - return timestampGetDayOfYear(ts, utcTZ) - }), - ), - MemberOverload(overloads.TimestampToDayOfYearWithTz, []*Type{TimestampType, StringType}, IntType, - BinaryBinding(func(ts, tz ref.Val) ref.Val { - return timestampGetDayOfYear(ts, tz) + t := ts.(types.Timestamp) + return t.Receive(overloads.TimeGetDayOfYear, overloads.TimestampToDayOfYear, []ref.Val{}) }), ), ), Function(overloads.TimeGetDayOfMonth, MemberOverload(overloads.TimestampToDayOfMonthZeroBased, []*Type{TimestampType}, IntType, UnaryBinding(func(ts ref.Val) ref.Val { - return timestampGetDayOfMonthZeroBased(ts, utcTZ) + t := ts.(types.Timestamp) + return t.Receive(overloads.TimeGetDayOfMonth, overloads.TimestampToDayOfMonthZeroBased, []ref.Val{}) }), ), - MemberOverload(overloads.TimestampToDayOfMonthZeroBasedWithTz, []*Type{TimestampType, StringType}, IntType, - BinaryBinding(timestampGetDayOfMonthZeroBased), - ), ), Function(overloads.TimeGetDate, MemberOverload(overloads.TimestampToDayOfMonthOneBased, []*Type{TimestampType}, IntType, UnaryBinding(func(ts ref.Val) ref.Val { - return timestampGetDayOfMonthOneBased(ts, utcTZ) + t := ts.(types.Timestamp) + return t.Receive(overloads.TimeGetDate, overloads.TimestampToDayOfMonthOneBased, []ref.Val{}) }), ), - MemberOverload(overloads.TimestampToDayOfMonthOneBasedWithTz, []*Type{TimestampType, StringType}, IntType, - BinaryBinding(timestampGetDayOfMonthOneBased), - ), ), Function(overloads.TimeGetDayOfWeek, MemberOverload(overloads.TimestampToDayOfWeek, []*Type{TimestampType}, IntType, UnaryBinding(func(ts ref.Val) ref.Val { - return timestampGetDayOfWeek(ts, utcTZ) + t := ts.(types.Timestamp) + return t.Receive(overloads.TimeGetDayOfWeek, overloads.TimestampToDayOfWeek, []ref.Val{}) }), ), - MemberOverload(overloads.TimestampToDayOfWeekWithTz, []*Type{TimestampType, StringType}, IntType, - BinaryBinding(timestampGetDayOfWeek), - ), ), Function(overloads.TimeGetHours, MemberOverload(overloads.TimestampToHours, []*Type{TimestampType}, IntType, UnaryBinding(func(ts ref.Val) ref.Val { - return timestampGetHours(ts, utcTZ) + t := ts.(types.Timestamp) + return t.Receive(overloads.TimeGetHours, overloads.TimestampToHours, []ref.Val{}) }), ), - MemberOverload(overloads.TimestampToHoursWithTz, []*Type{TimestampType, StringType}, IntType, - BinaryBinding(timestampGetHours), - ), ), Function(overloads.TimeGetMinutes, MemberOverload(overloads.TimestampToMinutes, []*Type{TimestampType}, IntType, UnaryBinding(func(ts ref.Val) ref.Val { - return timestampGetMinutes(ts, utcTZ) + t := ts.(types.Timestamp) + return t.Receive(overloads.TimeGetMinutes, overloads.TimestampToMinutes, []ref.Val{}) }), ), - MemberOverload(overloads.TimestampToMinutesWithTz, []*Type{TimestampType, StringType}, IntType, - BinaryBinding(timestampGetMinutes), - ), ), Function(overloads.TimeGetSeconds, MemberOverload(overloads.TimestampToSeconds, []*Type{TimestampType}, IntType, UnaryBinding(func(ts ref.Val) ref.Val { - return timestampGetSeconds(ts, utcTZ) + t := ts.(types.Timestamp) + return t.Receive(overloads.TimeGetSeconds, overloads.TimestampToSeconds, []ref.Val{}) }), ), - MemberOverload(overloads.TimestampToSecondsWithTz, []*Type{TimestampType, StringType}, IntType, - BinaryBinding(timestampGetSeconds), - ), ), Function(overloads.TimeGetMilliseconds, MemberOverload(overloads.TimestampToMilliseconds, []*Type{TimestampType}, IntType, UnaryBinding(func(ts ref.Val) ref.Val { - return timestampGetMilliseconds(ts, utcTZ) + t := ts.(types.Timestamp) + return t.Receive(overloads.TimeGetMilliseconds, overloads.TimestampToMilliseconds, []ref.Val{}) }), ), - MemberOverload(overloads.TimestampToMillisecondsWithTz, []*Type{TimestampType, StringType}, IntType, - BinaryBinding(timestampGetMilliseconds), - ), ), } ) - -func timestampGetFullYear(ts, tz ref.Val) ref.Val { - t, err := inTimeZone(ts, tz) - if err != nil { - return types.NewErrFromString(err.Error()) - } - return types.Int(t.Year()) -} - -func timestampGetMonth(ts, tz ref.Val) ref.Val { - t, err := inTimeZone(ts, tz) - if err != nil { - return types.NewErrFromString(err.Error()) - } - // CEL spec indicates that the month should be 0-based, but the Time value - // for Month() is 1-based. - return types.Int(t.Month() - 1) -} - -func timestampGetDayOfYear(ts, tz ref.Val) ref.Val { - t, err := inTimeZone(ts, tz) - if err != nil { - return types.NewErrFromString(err.Error()) - } - return types.Int(t.YearDay() - 1) -} - -func timestampGetDayOfMonthZeroBased(ts, tz ref.Val) ref.Val { - t, err := inTimeZone(ts, tz) - if err != nil { - return types.NewErrFromString(err.Error()) - } - return types.Int(t.Day() - 1) -} - -func timestampGetDayOfMonthOneBased(ts, tz ref.Val) ref.Val { - t, err := inTimeZone(ts, tz) - if err != nil { - return types.NewErrFromString(err.Error()) - } - return types.Int(t.Day()) -} - -func timestampGetDayOfWeek(ts, tz ref.Val) ref.Val { - t, err := inTimeZone(ts, tz) - if err != nil { - return types.NewErrFromString(err.Error()) - } - return types.Int(t.Weekday()) -} - -func timestampGetHours(ts, tz ref.Val) ref.Val { - t, err := inTimeZone(ts, tz) - if err != nil { - return types.NewErrFromString(err.Error()) - } - return types.Int(t.Hour()) -} - -func timestampGetMinutes(ts, tz ref.Val) ref.Val { - t, err := inTimeZone(ts, tz) - if err != nil { - return types.NewErrFromString(err.Error()) - } - return types.Int(t.Minute()) -} - -func timestampGetSeconds(ts, tz ref.Val) ref.Val { - t, err := inTimeZone(ts, tz) - if err != nil { - return types.NewErrFromString(err.Error()) - } - return types.Int(t.Second()) -} - -func timestampGetMilliseconds(ts, tz ref.Val) ref.Val { - t, err := inTimeZone(ts, tz) - if err != nil { - return types.NewErrFromString(err.Error()) - } - return types.Int(t.Nanosecond() / 1000000) -} - -func inTimeZone(ts, tz ref.Val) (time.Time, error) { - t := ts.(types.Timestamp) - val := string(tz.(types.String)) - ind := strings.Index(val, ":") - if ind == -1 { - loc, err := time.LoadLocation(val) - if err != nil { - return time.Time{}, err - } - return t.In(loc), nil - } - - // 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. - hr, err := strconv.Atoi(string(val[0:ind])) - if err != nil { - return time.Time{}, err - } - min, err := strconv.Atoi(string(val[ind+1:])) - if err != nil { - return time.Time{}, err - } - var offset int - if string(val[0]) == "-" { - offset = hr*60 - min - } else { - offset = hr*60 + min - } - secondsEastOfUTC := int((time.Duration(offset) * time.Minute).Seconds()) - timezone := time.FixedZone("", secondsEastOfUTC) - return t.In(timezone), nil -} diff --git a/vendor/github.com/google/cel-go/cel/macro.go b/vendor/github.com/google/cel-go/cel/macro.go index 4db1fd57a9cc1..3d3c5be1b7cf4 100644 --- a/vendor/github.com/google/cel-go/cel/macro.go +++ b/vendor/github.com/google/cel-go/cel/macro.go @@ -142,24 +142,38 @@ type MacroExprHelper interface { NewError(exprID int64, message string) *Error } +// MacroOpt defines a functional option for configuring macro behavior. +type MacroOpt = parser.MacroOpt + +// MacroDocs configures a list of strings into a multiline description for the macro. +func MacroDocs(docs ...string) MacroOpt { + return parser.MacroDocs(docs...) +} + +// MacroExamples configures a list of examples, either as a string or common.MultilineString, +// into an example set to be provided with the macro Documentation() call. +func MacroExamples(examples ...string) MacroOpt { + return parser.MacroExamples(examples...) +} + // GlobalMacro creates a Macro for a global function with the specified arg count. -func GlobalMacro(function string, argCount int, factory MacroFactory) Macro { - return parser.NewGlobalMacro(function, argCount, factory) +func GlobalMacro(function string, argCount int, factory MacroFactory, opts ...MacroOpt) Macro { + return parser.NewGlobalMacro(function, argCount, factory, opts...) } // ReceiverMacro creates a Macro for a receiver function matching the specified arg count. -func ReceiverMacro(function string, argCount int, factory MacroFactory) Macro { - return parser.NewReceiverMacro(function, argCount, factory) +func ReceiverMacro(function string, argCount int, factory MacroFactory, opts ...MacroOpt) Macro { + return parser.NewReceiverMacro(function, argCount, factory, opts...) } // GlobalVarArgMacro creates a Macro for a global function with a variable arg count. -func GlobalVarArgMacro(function string, factory MacroFactory) Macro { - return parser.NewGlobalVarArgMacro(function, factory) +func GlobalVarArgMacro(function string, factory MacroFactory, opts ...MacroOpt) Macro { + return parser.NewGlobalVarArgMacro(function, factory, opts...) } // ReceiverVarArgMacro creates a Macro for a receiver function matching a variable arg count. -func ReceiverVarArgMacro(function string, factory MacroFactory) Macro { - return parser.NewReceiverVarArgMacro(function, factory) +func ReceiverVarArgMacro(function string, factory MacroFactory, opts ...MacroOpt) Macro { + return parser.NewReceiverVarArgMacro(function, factory, opts...) } // NewGlobalMacro creates a Macro for a global function with the specified arg count. diff --git a/vendor/github.com/google/cel-go/cel/optimizer.go b/vendor/github.com/google/cel-go/cel/optimizer.go index 9a2a97a6477cd..6e260a93cf837 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 85f777e959e59..540ad38ba018a 100644 --- a/vendor/github.com/google/cel-go/cel/options.go +++ b/vendor/github.com/google/cel-go/cel/options.go @@ -15,6 +15,7 @@ package cel import ( + "errors" "fmt" "google.golang.org/protobuf/proto" @@ -23,8 +24,11 @@ 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" + "github.com/google/cel-go/common/env" "github.com/google/cel-go/common/functions" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/pb" @@ -68,8 +72,77 @@ 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) { + name, found := featureIDsToNames[id] + return name, found +} + +func featureIDByName(name string) (int, bool) { + for id, n := range featureIDsToNames { + if n == name { + return id, true + } + } + 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 +) + +// 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", +} + +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) @@ -112,6 +185,8 @@ func CustomTypeProvider(provider any) EnvOption { // Note: Declarations will by default be appended to the pre-existing declaration set configured // for the environment. The NewEnv call builds on top of the standard CEL declarations. For a // purely custom set of declarations use NewCustomEnv. +// +// Deprecated: use FunctionDecls and VariableDecls or FromConfig instead. func Declarations(decls ...*exprpb.Decl) EnvOption { declOpts := []EnvOption{} var err error @@ -250,9 +325,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 } @@ -269,7 +344,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) } @@ -306,7 +381,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) } @@ -354,7 +429,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) @@ -362,7 +437,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) @@ -371,6 +446,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) @@ -379,7 +463,15 @@ type ProgramOption func(p *prog) (*prog, error) // InterpretableDecorators can be used to inspect, alter, or replace the Program plan. func CustomDecorator(dec interpreter.InterpretableDecorator) ProgramOption { return func(p *prog) (*prog, error) { - p.decorators = append(p.decorators, dec) + p.plannerOptions = append(p.plannerOptions, interpreter.CustomDecorator(dec)) + return p, nil + } +} + +// 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 } } @@ -401,10 +493,10 @@ func Functions(funcs ...*functions.Overload) ProgramOption { // variables with the same name provided to the Eval() call. If Globals is used in a Library with // a Lib EnvOption, vars may shadow variables provided by previously added libraries. // -// The vars value may either be an `interpreter.Activation` instance or a `map[string]any`. +// The vars value may either be an `cel.Activation` instance or a `map[string]any`. func Globals(vars any) ProgramOption { return func(p *prog) (*prog, error) { - defaultVars, err := interpreter.NewActivation(vars) + defaultVars, err := NewActivation(vars) if err != nil { return nil, err } @@ -426,6 +518,180 @@ func OptimizeRegex(regexOptimizations ...*interpreter.RegexOptimization) Program } } +// ConfigOptionFactory declares a signature which accepts a configuration element, e.g. env.Extension +// and optionally produces an EnvOption in response. +// +// If there are multiple ConfigOptionFactory values which could apply to the same configuration node +// the first one that returns an EnvOption and a `true` response will be used, and the config node +// will not be passed along to any other option factory. +// +// Only the *env.Extension type is provided at this time, but validators, optimizers, and other tuning +// parameters may be supported in the future. +type ConfigOptionFactory func(any) (EnvOption, bool) + +// FromConfig produces and applies a set of EnvOption values derived from an env.Config object. +// +// For configuration elements which refer to features outside of the `cel` package, an optional set of +// ConfigOptionFactory values may be passed in to support the conversion from static configuration to +// configured cel.Env value. +// +// Note: disabling the standard library will clear the EnvOptions values previously set for the +// environment with the exception of propagating types and adapters over to the new environment. +// +// Note: to support custom types referenced in the configuration file, you must ensure that one of +// the following options appears before the FromConfig option: Types, TypeDescs, or CustomTypeProvider +// as the type provider configured at the time when the config is processed is the one used to derive +// type references from the configuration. +func FromConfig(config *env.Config, optFactories ...ConfigOptionFactory) EnvOption { + return func(e *Env) (*Env, error) { + if err := config.Validate(); err != nil { + return nil, err + } + opts, err := configToEnvOptions(config, e.CELTypeProvider(), optFactories) + if err != nil { + return nil, err + } + for _, o := range opts { + e, err = o(e) + if err != nil { + return nil, err + } + } + return e, nil + } +} + +// configToEnvOptions generates a set of EnvOption values (or error) based on a config, a type provider, +// and an optional set of environment options. +func configToEnvOptions(config *env.Config, provider types.Provider, optFactories []ConfigOptionFactory) ([]EnvOption, error) { + envOpts := []EnvOption{} + // Configure the standard lib subset. + if config.StdLib != nil { + envOpts = append(envOpts, func(e *Env) (*Env, error) { + if e.HasLibrary("cel.lib.std") { + return nil, errors.New("invalid subset of stdlib: create a custom env") + } + return e, nil + }) + if !config.StdLib.Disabled { + envOpts = append(envOpts, StdLib(StdLibSubset(config.StdLib))) + } + } else { + envOpts = append(envOpts, StdLib()) + } + + // Configure the container + if config.Container != "" { + envOpts = append(envOpts, Container(config.Container)) + } + + // Configure abbreviations + for _, imp := range config.Imports { + 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 + if _, found := provider.FindStructType(typeName); !found { + return nil, fmt.Errorf("invalid context proto type: %q", typeName) + } + // Attempt to instantiate the proto in order to reflect to its descriptor + msg := provider.NewValue(typeName, map[string]ref.Val{}) + pbMsg, ok := msg.Value().(proto.Message) + if !ok { + return nil, fmt.Errorf("unsupported context type: %T", msg.Value()) + } + envOpts = append(envOpts, DeclareContextProto(pbMsg.ProtoReflect().Descriptor())) + } + + // Configure variables + if len(config.Variables) != 0 { + vars := make([]*decls.VariableDecl, 0, len(config.Variables)) + for _, v := range config.Variables { + vDef, err := v.AsCELVariable(provider) + if err != nil { + return nil, err + } + vars = append(vars, vDef) + } + envOpts = append(envOpts, VariableDecls(vars...)) + } + + // Configure functions + if len(config.Functions) != 0 { + funcs := make([]*decls.FunctionDecl, 0, len(config.Functions)) + for _, f := range config.Functions { + fnDef, err := f.AsCELFunction(provider) + if err != nil { + return nil, err + } + funcs = append(funcs, fnDef) + } + envOpts = append(envOpts, FunctionDecls(funcs...)) + } + + for _, limit := range config.Limits { + if id, found := limitIDByName(limit.Name); found { + envOpts = append(envOpts, setLimit(id, limit.Value)) + } + } + + // Configure validators + for _, val := range config.Validators { + if fac, found := astValidatorFactories[val.Name]; found { + envOpts = append(envOpts, func(e *Env) (*Env, error) { + validator, err := fac(val) + if err != nil { + return nil, fmt.Errorf("%w", err) + } + return ASTValidators(validator)(e) + }) + } else if opt, handled := handleExtendedConfigOption(val, optFactories); handled { + envOpts = append(envOpts, opt) + } + // we don't error when the validator isn't found as it may be part + // of an extension library and enabled implicitly. + } + + // Configure extensions + for _, ext := range config.Extensions { + // version number has been validated by the call to `Validate` + ver, _ := ext.VersionNumber() + if ext.Name == "optional" { + envOpts = append(envOpts, OptionalTypes(OptionalTypesVersion(ver))) + } else { + opt, handled := handleExtendedConfigOption(ext, optFactories) + if !handled { + return nil, fmt.Errorf("unrecognized extension: %s", ext.Name) + } + envOpts = append(envOpts, opt) + } + } + + return envOpts, nil +} + +func handleExtendedConfigOption(conf any, optFactories []ConfigOptionFactory) (EnvOption, bool) { + for _, optFac := range optFactories { + if opt, useOption := optFac(conf); useOption { + return opt, true + } + } + return nil, false +} + // EvalOption indicates an evaluation option that may affect the evaluation behavior or information // in the output result. type EvalOption int @@ -481,6 +747,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) { @@ -534,8 +841,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) (EnvOption, 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() @@ -547,38 +857,49 @@ func fieldToVariable(field protoreflect.FieldDescriptor) (EnvOption, error) { if err != nil { return nil, err } - return Variable(name, MapType(keyType, valueType)), nil + return decls.NewVariable(name, MapType(keyType, valueType)), nil } if field.IsList() { elemType, err := fieldToCELType(field) if err != nil { return nil, err } - return Variable(name, ListType(elemType)), nil + return decls.NewVariable(name, ListType(elemType)), nil } celType, err := fieldToCELType(field) if err != nil { return nil, err } - return Variable(name, celType), nil + return decls.NewVariable(name, celType), nil } // 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 { + return nil, fmt.Errorf("context proto already declared as %q, got %q", + e.contextProto.FullName(), descriptor.FullName()) + } + 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) - if err != nil { - return nil, err - } - e, err = variable(e) + variable, err := fieldToVariable(field, jsonFieldNames) if err != nil { return nil, err } + vars = append(vars, variable) + } + var err error + e, err = VariableDecls(vars...)(e) + if err != nil { + return nil, err } return Types(dynamicpb.NewMessage(descriptor))(e) } @@ -588,11 +909,15 @@ 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) (interpreter.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...) + reg, err := types.NewProtoRegistry(regOpts...) if err != nil { return nil, err } @@ -602,17 +927,21 @@ func ContextProtoVars(ctx proto.Message) (interpreter.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 interpreter.NewActivation(vars) + return NewActivation(vars) } // EnableMacroCallTracking ensures that call expressions which are replaced by macros @@ -646,22 +975,51 @@ func features(flag int, enabled bool) EnvOption { } } -// 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.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 func(e *Env) (*Env, error) { - e.prsrOpts = append(e.prsrOpts, parser.ExpressionSizeCodePointLimit(limit)) - return e, nil - } + 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) } // EnableHiddenAccumulatorName sets the parser to use the identifier '@result' for accumulators diff --git a/vendor/github.com/google/cel-go/cel/program.go b/vendor/github.com/google/cel-go/cel/program.go index 49bd537838322..3a7589a71cf75 100644 --- a/vendor/github.com/google/cel-go/cel/program.go +++ b/vendor/github.com/google/cel-go/cel/program.go @@ -16,10 +16,13 @@ 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/functions" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" "github.com/google/cel-go/interpreter" @@ -29,7 +32,7 @@ import ( type Program interface { // Eval returns the result of an evaluation of the Ast and environment against the input vars. // - // The vars value may either be an `interpreter.Activation` or a `map[string]any`. + // The vars value may either be an `Activation` or a `map[string]any`. // // If the `OptTrackState`, `OptTrackCost` or `OptExhaustiveEval` flags are used, the `details` response will // be non-nil. Given this caveat on `details`, the return state from evaluation will be: @@ -47,14 +50,54 @@ type Program interface { // to support cancellation and timeouts. This method must be used in conjunction with the // InterruptCheckFrequency() option for cancellation interrupts to be impact evaluation. // - // The vars value may either be an `interpreter.Activation` or `map[string]any`. + // The vars value may either be an `Activation` or `map[string]any`. // // 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. +// +// An Activation is the primary mechanism by which a caller supplies input into a CEL program. +type Activation = interpreter.Activation + +// NewActivation returns an activation based on a map-based binding where the map keys are +// expected to be qualified names used with ResolveName calls. +// +// The input `bindings` may either be of type `Activation` or `map[string]any`. +// +// Lazy bindings may be supplied within the map-based input in either of the following forms: +// - func() any +// - func() ref.Val +// +// The output of the lazy binding will overwrite the variable reference in the internal map. +// +// 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 NewActivation(bindings any) (Activation, error) { + return interpreter.NewActivation(bindings) +} + +// PartialActivation extends the Activation interface with a set of unknown AttributePatterns. +type PartialActivation = interpreter.PartialActivation + // NoVars returns an empty Activation. -func NoVars() interpreter.Activation { +func NoVars() Activation { return interpreter.EmptyActivation() } @@ -64,10 +107,9 @@ func NoVars() interpreter.Activation { // This method relies on manually configured sets of missing attribute patterns. For a method which // infers the missing variables from the input and the configured environment, use Env.PartialVars(). // -// The `vars` value may either be an interpreter.Activation or any valid input to the -// interpreter.NewActivation call. +// The `vars` value may either be an Activation or any valid input to the NewActivation call. func PartialVars(vars any, - unknowns ...*interpreter.AttributePattern) (interpreter.PartialActivation, error) { + unknowns ...*AttributePatternType) (PartialActivation, error) { return interpreter.NewPartialActivation(vars, unknowns...) } @@ -84,12 +126,15 @@ func PartialVars(vars any, // fully qualified variable name may be `ns.app.a`, `ns.a`, or `a` per the CEL namespace resolution // rules. Pick the fully qualified variable name that makes sense within the container as the // AttributePattern `varName` argument. +func AttributePattern(varName string) *AttributePatternType { + return interpreter.NewAttributePattern(varName) +} + +// AttributePatternType represents a top-level variable with an optional set of qualifier patterns. // // See the interpreter.AttributePattern and interpreter.AttributeQualifierPattern for more info // about how to create and manipulate AttributePattern values. -func AttributePattern(varName string) *interpreter.AttributePattern { - return interpreter.NewAttributePattern(varName) -} +type AttributePatternType = interpreter.AttributePattern // EvalDetails holds additional information observed during the Eval() call. type EvalDetails struct { @@ -116,39 +161,43 @@ 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 evalOpts EvalOption - defaultVars interpreter.Activation + defaultVars Activation dispatcher interpreter.Dispatcher interpreter interpreter.Interpreter interruptCheckFrequency uint // Intermediate state used to configure the InterpretableDecorator set provided // to the initInterpretable call. - decorators []interpreter.InterpretableDecorator + plannerOptions []interpreter.PlannerOption 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 -} -func (p *prog) clone() *prog { - costOptsCopy := make([]interpreter.CostTrackerOption, len(p.costOptions)) - copy(costOptsCopy, p.costOptions) - - return &prog{ - Env: p.Env, - evalOpts: p.evalOpts, - defaultVars: p.defaultVars, - dispatcher: p.dispatcher, - interpreter: p.interpreter, - interruptCheckFrequency: p.interruptCheckFrequency, - } + // 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 } // newProgram creates a program instance with an environment, an ast, and an optional list of @@ -162,10 +211,11 @@ func newProgram(e *Env, a *ast.AST, opts []ProgramOption) (Program, error) { // Ensure the default attribute factory is set after the adapter and provider are // configured. p := &prog{ - Env: e, - decorators: []interpreter.InterpretableDecorator{}, - dispatcher: disp, - costOptions: []interpreter.CostTrackerOption{}, + Env: e, + plannerOptions: []interpreter.PlannerOption{}, + dispatcher: disp, + costOptions: []interpreter.CostTrackerOption{}, + drainStrategy: async.DrainReady(100 * time.Microsecond), } // Configure the program via the ProgramOption values. @@ -177,15 +227,35 @@ 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 + e.funcBindOnce.Do(func() { + var bindings []*functions.Overload + e.functionBindings = []*functions.Overload{} + for _, fn := range e.functions { + bindings, err = fn.Bindings() + if err != nil { + return + } + e.functionBindings = append(e.functionBindings, bindings...) } - err = disp.Add(bindings...) - if err != nil { - return nil, err + }) + if err != nil { + return nil, err + } + + // Add the function bindings created via Function() options. + err = disp.Add(e.functionBindings...) + if err != nil { + return nil, err + } + + // Determine whether the environment declares any asynchronous function. Async is a property of + // the binding, so its presence is known from the environment alone, without inspecting the + // program plan. The synchronous entry points (Eval, ContextEval) reject programs from an env + // with async functions; callers needing synchronous evaluation should use a non-async env. + for _, b := range e.functionBindings { + if b.Async != nil { + p.hasAsync = true + break } } @@ -194,6 +264,12 @@ func newProgram(e *Env, a *ast.AST, opts []ProgramOption) (Program, error) { 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 { @@ -203,74 +279,79 @@ func newProgram(e *Env, a *ast.AST, opts []ProgramOption) (Program, error) { p.interpreter = interp // Translate the EvalOption flags into InterpretableDecorator instances. - decorators := make([]interpreter.InterpretableDecorator, len(p.decorators)) - copy(decorators, p.decorators) + plannerOptions := make([]interpreter.PlannerOption, len(p.plannerOptions)) + copy(plannerOptions, p.plannerOptions) // Enable interrupt checking if there's a non-zero check frequency if p.interruptCheckFrequency > 0 { - decorators = append(decorators, interpreter.InterruptableEval()) + plannerOptions = append(plannerOptions, interpreter.InterruptableEval()) } // Enable constant folding first. if p.evalOpts&OptOptimize == OptOptimize { - decorators = append(decorators, interpreter.Optimize()) + plannerOptions = append(plannerOptions, interpreter.Optimize()) p.regexOptimizations = append(p.regexOptimizations, interpreter.MatchesRegexOptimization) } // Enable regex compilation of constants immediately after folding constants. if len(p.regexOptimizations) > 0 { - decorators = append(decorators, interpreter.CompileRegexConstants(p.regexOptimizations...)) + plannerOptions = append(plannerOptions, interpreter.CompileRegexConstants(p.regexOptimizations...)) } // Enable exhaustive eval, state tracking and cost tracking last since they require a factory. if p.evalOpts&(OptExhaustiveEval|OptTrackState|OptTrackCost) != 0 { - factory := func(state interpreter.EvalState, costTracker *interpreter.CostTracker) (Program, error) { - costTracker.Estimator = p.callCostEstimator - costTracker.Limit = p.costLimit - for _, costOpt := range p.costOptions { - err := costOpt(costTracker) - if err != nil { - return nil, err - } - } - // Limit capacity to guarantee a reallocation when calling 'append(decs, ...)' below. This - // prevents the underlying memory from being shared between factory function calls causing - // undesired mutations. - decs := decorators[:len(decorators):len(decorators)] - var observers []interpreter.EvalObserver - - if p.evalOpts&(OptExhaustiveEval|OptTrackState) != 0 { - // EvalStateObserver is required for OptExhaustiveEval. - observers = append(observers, interpreter.EvalStateObserver(state)) - } - if p.evalOpts&OptTrackCost == OptTrackCost { - observers = append(observers, interpreter.CostObserver(costTracker)) - } - - // Enable exhaustive eval over a basic observer since it offers a superset of features. - if p.evalOpts&OptExhaustiveEval == OptExhaustiveEval { - decs = append(decs, interpreter.ExhaustiveEval(), interpreter.Observe(observers...)) - } else if len(observers) > 0 { - decs = append(decs, interpreter.Observe(observers...)) - } - - return p.clone().initInterpretable(a, decs) + costOptCount := len(p.costOptions) + if p.costLimit != nil { + costOptCount++ + } + costOpts := make([]interpreter.CostTrackerOption, 0, costOptCount) + costOpts = append(costOpts, p.costOptions...) + 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 tracker.Clone() + } + var observers []interpreter.PlannerOption + if p.evalOpts&(OptExhaustiveEval|OptTrackState) != 0 { + // EvalStateObserver is required for OptExhaustiveEval. + observers = append(observers, interpreter.EvalStateObserver()) + } + if p.evalOpts&OptTrackCost == OptTrackCost { + observers = append(observers, interpreter.CostObserver(interpreter.CostTrackerFactory(trackerFactory))) + } + // Enable exhaustive eval over a basic observer since it offers a superset of features. + if p.evalOpts&OptExhaustiveEval == OptExhaustiveEval { + plannerOptions = append(plannerOptions, + append([]interpreter.PlannerOption{interpreter.ExhaustiveEval()}, observers...)...) + } else if len(observers) > 0 { + plannerOptions = append(plannerOptions, observers...) } - return newProgGen(factory) } - return p.initInterpretable(a, decorators) + return p.initInterpretable(a, plannerOptions) } -func (p *prog) initInterpretable(a *ast.AST, decs []interpreter.InterpretableDecorator) (*prog, error) { +func (p *prog) initInterpretable(a *ast.AST, plannerOptions []interpreter.PlannerOption) (*prog, error) { // When the AST has been exprAST it contains metadata that can be used to speed up program execution. - interpretable, err := p.interpreter.NewInterpretable(a, decs...) + interpretable, err := p.interpreter.NewInterpretable(a, plannerOptions...) if err != nil { return nil, err } p.interpretable = interpretable + if oi, ok := interpretable.(*interpreter.ObservableInterpretable); ok { + p.observable = oi + } return p, nil } // Eval implements the Program interface method. -func (p *prog) Eval(input any) (v ref.Val, det *EvalDetails, err error) { +func (p *prog) Eval(input any) (out ref.Val, det *EvalDetails, err error) { // Configure error recovery for unexpected panics during evaluation. Note, the use of named // return values makes it possible to modify the error response during the recovery // function. @@ -284,26 +365,40 @@ func (p *prog) Eval(input any) (v ref.Val, det *EvalDetails, err error) { } } }() - // Build a hierarchical activation if there are default vars set. - var vars interpreter.Activation - switch v := input.(type) { - case interpreter.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.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) } - v = p.interpretable.Eval(vars) // 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 // RPC signature which allows for multiple errors to be returned, but should be sufficient. - if types.IsError(v) { - err = v.(*types.Err) + if types.IsError(out) { + err = out.(*types.Err) } return } @@ -313,237 +408,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 interpreter.Activation - switch v := input.(type) { - case interpreter.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 + } + 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 p.Eval(vars) + return out, det, errEval } -// progFactory is a helper alias for marking a program creation factory function. -type progFactory func(interpreter.EvalState, *interpreter.CostTracker) (Program, error) +// 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) + } -// progGen holds a reference to a progFactory instance and implements the Program interface. -type progGen struct { - factory progFactory + return frame, nil } -// newProgGen tests the factory object by calling it once and returns a factory-based Program if -// the test is successful. -func newProgGen(factory progFactory) (Program, error) { - // Test the factory to make sure that configuration errors are spotted at config - tracker, err := interpreter.NewCostTracker(nil) +// 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 } - _, err = factory(interpreter.NewEvalState(), tracker) - if err != nil { + if err := frame.SetContext(ctx, p.interruptCheckFrequency); err != nil { + frame.Close() return nil, err } - return &progGen{factory: factory}, nil + frame.SetAsyncObserver(p.asyncObserver) + frame.SetAsyncMaxConcurrency(resolveAsyncMaxConcurrency(p.asyncMaxConcurrency)) + return frame, nil } -// Eval implements the Program interface method. -func (gen *progGen) Eval(input any) (ref.Val, *EvalDetails, error) { - // The factory based Eval() differs from the standard evaluation model in that it generates a - // new EvalState instance for each call to ensure that unique evaluations yield unique stateful - // results. - state := interpreter.NewEvalState() - costTracker, err := interpreter.NewCostTracker(nil) - if err != nil { - return nil, nil, err - } - det := &EvalDetails{state: state, costTracker: costTracker} - - // Generate a new instance of the interpretable using the factory configured during the call to - // newProgram(). It is incredibly unlikely that the factory call will generate an error given - // the factory test performed within the Program() call. - p, err := gen.factory(state, costTracker) - if err != nil { - return nil, det, err - } +// 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 - // Evaluate the input, returning the result and the 'state' within EvalDetails. - v, _, err := p.Eval(input) - if err != nil { - return v, det, err +// 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 v, det, nil + return configured } -// ContextEval implements the Program interface method. -func (gen *progGen) ContextEval(ctx context.Context, input any) (ref.Val, *EvalDetails, error) { - if ctx == nil { - return nil, nil, fmt.Errorf("context can not be nil") +// 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 } - // The factory based Eval() differs from the standard evaluation model in that it generates a - // new EvalState instance for each call to ensure that unique evaluations yield unique stateful - // results. - state := interpreter.NewEvalState() - costTracker, err := interpreter.NewCostTracker(nil) - if err != nil { - return nil, nil, err + limit := resolveAsyncMaxConcurrency(p.asyncMaxConcurrency) + if limit < 0 { + // Unlimited launches: fall back to the default bound for the buffer so it stays finite. + return defaultAsyncMaxConcurrency } - det := &EvalDetails{state: state, costTracker: costTracker} - - // Generate a new instance of the interpretable using the factory configured during the call to - // newProgram(). It is incredibly unlikely that the factory call will generate an error given - // the factory test performed within the Program() call. - p, err := gen.factory(state, costTracker) - if err != nil { - return nil, det, err - } - - // Evaluate the input, returning the result and the 'state' within EvalDetails. - v, _, err := p.ContextEval(ctx, input) - if err != nil { - return v, det, err - } - return v, det, nil + return limit } -type ctxEvalActivation struct { - parent interpreter.Activation - interrupt <-chan struct{} - interruptCheckCount uint - interruptCheckFrequency uint -} - -// 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 +// 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 + } + + 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)} + } } - } - return nil, false - } - return a.parent.ResolveName(name) -} + }() -func (a *ctxEvalActivation) Parent() interpreter.Activation { - return a.parent -} - -func newCtxEvalActivationPool() *ctxEvalActivationPool { - return &ctxEvalActivationPool{ - Pool: sync.Pool{ - New: func() any { - return &ctxEvalActivation{} - }, - }, - } -} + frame, err := p.newAsyncFrame(ctx, input) + if err != nil { + resCh <- EvalResult{Err: err} + return + } + 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) + } -type ctxEvalActivationPool struct { - sync.Pool -} + // 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 + } -// Setup initializes a pooled Activation with the ability check for context.Context cancellation -func (p *ctxEvalActivationPool) Setup(vars interpreter.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 -} + // 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 evalActivation struct { - vars map[string]any - lazyVars map[string]any -} + // Post-execution dispatch: launch only the async calls required by the unknown result. + frame.DispatchPendingAsyncCalls(unk.IDs()) -// 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 - } - lazy := obj() - a.lazyVars[name] = lazy - return lazy, true - default: - return obj, true - } -} + // 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 -// Parent implements the interpreter.Activation interface -func (a *evalActivation) Parent() interpreter.Activation { - return nil -} + // 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 + } -func newEvalActivationPool() *evalActivationPool { - return &evalActivationPool{ - Pool: sync.Pool{ - New: func() any { - return &evalActivation{lazyVars: make(map[string]any)} - }, - }, - } -} + // 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 + } -type evalActivationPool struct { - sync.Pool -} + 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 + } -// 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 -} + 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() + } + } + }() -func (p *evalActivationPool) Put(value any) { - a := value.(*evalActivation) - for k := range a.lazyVars { - delete(a.lazyVars, k) - } - p.Pool.Put(a) + return resCh } -var ( - // activationPool is an internally managed pool of Activation values that wrap map[string]any inputs - activationPool = newEvalActivationPool() - - // 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 new file mode 100644 index 0000000000000..f5993482718b0 --- /dev/null +++ b/vendor/github.com/google/cel-go/cel/prompt.go @@ -0,0 +1,233 @@ +// 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 +// +// https://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 cel + +import ( + _ "embed" + "sort" + "strings" + "text/template" + + "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": splitImpl, + "newlineToSpace": func(str string) string { return strings.ReplaceAll(str, "\n", " ") }, + } + tmpl := template.New("cel").Funcs(funcMap) + tmpl, err := tmpl.Parse(authoringPrompt) + if err != nil { + return nil, err + } + return &Prompt{ + Persona: defaultPersona, + FormatRules: defaultFormatRules, + GeneralUsage: defaultGeneralUsage, + tmpl: tmpl, + env: env, + }, 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 +// prompt to a human-readable string. +type Prompt struct { + // Persona indicates something about the kind of user making the request + Persona string + + // FormatRules indicate how the LLM should generate its output + FormatRules string + + // GeneralUsage specifies additional context on how CEL should be used. + GeneralUsage string + + // 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 []*promptVariable + Macros []*common.Doc + Functions []*common.Doc + UserPrompt string +} + +// 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([]*promptVariable, len(p.env.Variables())) + for i, v := range p.env.Variables() { + 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 + }) + macs := make([]*common.Doc, len(p.env.Macros())) + for i, m := range p.env.Macros() { + macs[i] = m.(common.Documentor).Documentation() + } + funcs := make([]*common.Doc, 0, len(p.env.Functions())) + for _, f := range p.env.Functions() { + if _, hidden := hiddenFunctions[f.Name()]; hidden { + continue + } + funcs = append(funcs, f.Documentation()) + } + sort.SliceStable(funcs, func(i, j int) bool { + return funcs[i].Name < funcs[j].Name + }) + inst := &promptInst{ + Prompt: p, + Variables: vars, + Macros: macs, + Functions: funcs, + UserPrompt: userPrompt, + } + p.tmpl.Execute(&buffer, inst) + return buffer.String() +} + +const ( + defaultPersona = `You are a software engineer with expertise in networking and application security +authoring boolean Common Expression Language (CEL) expressions to ensure firewall, +networking, authentication, and data access is only permitted when all conditions +are satisfied.` + + defaultFormatRules = `Output your response as a CEL expression. + +Write the expression with the comment on the first line and the expression on the +subsequent lines. Format the expression using 80-character line limits commonly +found in C++ or Java code.` + + defaultGeneralUsage = `CEL supports Protocol Buffer and JSON types, as well as simple types and aggregate types. + +Simple types include bool, bytes, double, int, string, and uint: + +* double literals must always include a decimal point: 1.0, 3.5, -2.2 +* uint literals must be positive values suffixed with a 'u': 42u +* byte literals are strings prefixed with a 'b': b'1235' +* string literals can use either single quotes or double quotes: 'hello', "world" +* string literals can also be treated as raw strings that do not require any + escaping within the string by using the 'R' prefix: R"""quote: "hi" """ + +Aggregate types include list and map: + +* list literals consist of zero or more values between brackets: "['a', 'b', 'c']" +* map literal consist of colon-separated key-value pairs within braces: "{'key1': 1, 'key2': 2}" +* Only int, uint, string, and bool types are valid map keys. +* Maps containing HTTP headers must always use lower-cased string keys. + +Comments start with two-forward slashes followed by text and a newline.` +) + +var ( + hiddenFunctions = map[string]bool{ + overloads.DeprecatedIn: true, + operators.OldIn: true, + operators.OldNotStrictlyFalse: true, + operators.NotStrictlyFalse: true, + } +) diff --git a/vendor/github.com/google/cel-go/cel/templates/authoring.tmpl b/vendor/github.com/google/cel-go/cel/templates/authoring.tmpl new file mode 100644 index 0000000000000..a921df9b0e662 --- /dev/null +++ b/vendor/github.com/google/cel-go/cel/templates/authoring.tmpl @@ -0,0 +1,78 @@ +{{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}} - {{newlineToSpace .Description}} +{{end}} +{{range .Children}}{{range split .Description}} {{.}} +{{end}} +{{- end -}} +{{- end -}} + +{{define "overload" -}} +{{if .Children}}{{range .Children}}{{range split .Description}} {{.}} +{{end}} +{{- end -}} +{{else}} {{.Signature}} +{{end}} +{{- end -}} + +{{define "function" -}} +{{.Name}}{{if .Description}} - {{newlineToSpace .Description}} +{{end}} +{{range .Children}}{{template "overload" .}}{{end}} +{{- end -}} + +{{.Persona}} + +{{.FormatRules}} + +{{if or .Variables .Macros .Functions -}} +Only use the following variables, macros, and functions in expressions. +{{if .Variables}} +Variables: + +{{range .Variables -}} +{{template "variable" .}} +{{end -}} +{{- end -}} + +{{if .Macros}} +Macros: + +{{range .Macros}}* {{template "macro" .}} +{{end -}} +{{end -}} + +{{if .Functions}} +Functions: + +{{range .Functions}}* {{template "function" .}} +{{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 b50c674520a75..cb7f4c29ea75b 100644 --- a/vendor/github.com/google/cel-go/cel/validator.go +++ b/vendor/github.com/google/cel-go/cel/validator.go @@ -15,16 +15,23 @@ package cel import ( + "context" "fmt" "reflect" "regexp" "github.com/google/cel-go/common/ast" + "github.com/google/cel-go/common/env" "github.com/google/cel-go/common/overloads" ) const ( - homogeneousValidatorName = "cel.lib.std.validate.types.homogeneous" + 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" // HomogeneousAggregateLiteralExemptFunctions is the ValidatorConfig key used to configure // the set of function names which are exempt from homogeneous type checks. The expected type @@ -36,14 +43,76 @@ const ( HomogeneousAggregateLiteralExemptFunctions = homogeneousValidatorName + ".exempt" ) +var ( + astValidatorFactories = map[string]ASTValidatorFactory{ + nestingLimitValidatorName: func(val *env.Validator) (ASTValidator, error) { + if limit, found := val.ConfigValue("limit"); found { + // In case of protos, config value is of type by google.protobuf.Value, which numeric values are always a double. + if val, isDouble := limit.(float64); isDouble { + if val != float64(int64(val)) { + return nil, fmt.Errorf("invalid validator: %s, limit value is not a whole number: %v", nestingLimitValidatorName, limit) + } + return ValidateComprehensionNestingLimit(int(val)), nil + } + + if val, isInt := limit.(int); isInt { + return ValidateComprehensionNestingLimit(val), nil + } + return nil, fmt.Errorf("invalid validator: %s unsupported limit type: %v", nestingLimitValidatorName, limit) + } + return nil, fmt.Errorf("invalid validator: %s missing limit", nestingLimitValidatorName) + }, + bindNestingLimitValidatorName: func(val *env.Validator) (ASTValidator, error) { + if limit, found := val.ConfigValue("limit"); found { + // In case of protos, config value is of type by google.protobuf.Value, which numeric values are always a double. + if val, isDouble := limit.(float64); isDouble { + if val != float64(int64(val)) { + return nil, fmt.Errorf("invalid validator: %s, limit value is not a whole number: %v", bindNestingLimitValidatorName, limit) + } + return ValidateBindNestingLimit(int(val)), nil + } + + if val, isInt := limit.(int); isInt { + return ValidateBindNestingLimit(val), nil + } + return nil, fmt.Errorf("invalid validator: %s unsupported limit type: %v", bindNestingLimitValidatorName, limit) + } + return nil, fmt.Errorf("invalid validator: %s missing limit", bindNestingLimitValidatorName) + }, + durationValidatorName: func(*env.Validator) (ASTValidator, error) { + return ValidateDurationLiterals(), nil + }, + regexValidatorName: func(*env.Validator) (ASTValidator, error) { + return ValidateRegexLiterals(), nil + }, + timestampValidatorName: func(*env.Validator) (ASTValidator, error) { + return ValidateTimestampLiterals(), nil + }, + homogeneousValidatorName: func(*env.Validator) (ASTValidator, error) { + return ValidateHomogeneousAggregateLiterals(), nil + }, + } +) + +// ASTValidatorFactory creates an ASTValidator as configured by the input map +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) } } @@ -70,6 +139,18 @@ type ASTValidator interface { Validate(*Env, ValidatorConfig, *ast.AST, *Issues) } +// ConfigurableASTValidator supports conversion of an object to an `env.Validator` instance used for +// YAML serialization. +type ConfigurableASTValidator interface { + // ToConfig converts the internal configuration of an ASTValidator into an env.Validator instance + // which minimally must include the validator name, but may also include a map[string]any config + // object to be serialized to YAML. The string keys represent the configuration parameter name, + // and the any value must mirror the internally supported type associated with the config key. + // + // Note: only primitive CEL types are supported by CEL validators at this time. + ToConfig() *env.Validator +} + // ValidatorConfig provides an accessor method for querying validator configuration state. type ValidatorConfig interface { GetOrDefault(name string, value any) any @@ -178,6 +259,13 @@ 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} +} + type argChecker func(env *Env, call, arg ast.Expr) error func newFormatValidator(funcName string, argNum int, check argChecker) formatValidator { @@ -196,7 +284,12 @@ type formatValidator struct { // Name returns the unique name of this function format validator. func (v formatValidator) Name() string { - return fmt.Sprintf("cel.lib.std.validate.functions.%s", v.funcName) + return fmt.Sprintf("cel.validator.%s", v.funcName) +} + +// ToConfig converts the ASTValidator to an env.Validator specifying the validator name. +func (v formatValidator) ToConfig() *env.Validator { + return env.NewValidator(v.Name()) } // Validate searches the AST for uses of a given function name with a constant argument and performs a check @@ -225,8 +318,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 { @@ -242,6 +336,11 @@ func (homogeneousAggregateLiteralValidator) Name() string { return homogeneousValidatorName } +// ToConfig converts the ASTValidator to an env.Validator specifying the validator name. +func (v homogeneousAggregateLiteralValidator) ToConfig() *env.Validator { + return env.NewValidator(v.Name()) +} + // Validate validates that all lists and map literals have homogeneous types, i.e. don't contain dyn types. // // This validator makes an exception for list and map literals which occur at any level of nesting within @@ -336,10 +435,18 @@ type nestingLimitValidator struct { limit int } +// Name returns the name of the nesting limit validator. func (v nestingLimitValidator) Name() string { - return "cel.lib.std.validate.comprehension_nesting_limit" + return nestingLimitValidatorName +} + +// ToConfig converts the ASTValidator to an env.Validator specifying the validator name and the nesting limit +// as an integer value: {"limit": int} +func (v nestingLimitValidator) ToConfig() *env.Validator { + return env.NewValidator(v.Name()).SetConfig(map[string]any{"limit": v.limit}) } +// Validate implements the ASTValidator interface method. func (v nestingLimitValidator) Validate(e *Env, _ ValidatorConfig, a *ast.AST, iss *Issues) { root := ast.NavigateAST(a) comprehensions := ast.MatchDescendants(root, ast.KindMatcher(ast.ComprehensionKind)) @@ -358,8 +465,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 } @@ -373,3 +479,68 @@ 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}) +} + +// 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 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() +} diff --git a/vendor/github.com/google/cel-go/checker/checker.go b/vendor/github.com/google/cel-go/checker/checker.go index 6824af7a541cc..42d27a4283cf6 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,9 +161,43 @@ 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() + if len(call.Args()) != 2 || call.IsMemberFunction() { + t := "" + if call.IsMemberFunction() { + t = " member call with" + } + c.errors.notAnOptionalFieldSelectionCall(e.ID(), c.location(e), + fmt.Sprintf( + "incorrect signature.%s argument count: %d", t, len(call.Args()))) + return + } + operand := call.Args()[0] field := call.Args()[1] fieldName, isString := maybeUnwrapString(field) @@ -223,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) @@ -245,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. @@ -258,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) @@ -430,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()) @@ -670,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 59be751c975ca..3d7dd7ec4a20f 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 { @@ -545,16 +550,17 @@ func (c *coster) costCall(e ast.Expr) CostEstimate { if len(overloadIDs) == 0 { return CostEstimate{} } - var targetType AstNode + var targetType *AstNode if call.IsMemberFunction() { sum = sum.Add(c.cost(call.Target())) - targetType = c.newAstNode(call.Target()) + var t AstNode = c.newAstNode(call.Target()) + targetType = &t } // Pick a cost estimate range that covers all the overload cost estimation ranges fnCost := CostEstimate{Min: uint64(math.MaxUint64), Max: 0} var resultSize *SizeEstimate for _, overload := range overloadIDs { - overloadCost := c.functionCost(e, call.FunctionName(), overload, &targetType, argTypes, argCosts) + overloadCost := c.functionCost(e, call.FunctionName(), overload, targetType, argTypes, argCosts) fnCost = fnCost.Union(overloadCost.CostEstimate) if overloadCost.ResultSize != nil { if resultSize == nil { @@ -785,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/decls/decls.go b/vendor/github.com/google/cel-go/checker/decls/decls.go index c0e5de469f585..e013d2c2b6d62 100644 --- a/vendor/github.com/google/cel-go/checker/decls/decls.go +++ b/vendor/github.com/google/cel-go/checker/decls/decls.go @@ -91,6 +91,17 @@ func NewFunction(name string, Overloads: overloads}}} } +// NewFunctionWithDoc creates a named function declaration with a description and one or more overloads. +func NewFunctionWithDoc(name, doc string, + overloads ...*exprpb.Decl_FunctionDecl_Overload) *exprpb.Decl { + return &exprpb.Decl{ + Name: name, + DeclKind: &exprpb.Decl_Function{ + Function: &exprpb.Decl_FunctionDecl{ + // Doc: desc, + Overloads: overloads}}} +} + // NewIdent creates a named identifier declaration with an optional literal // value. // @@ -98,28 +109,37 @@ func NewFunction(name string, // // Deprecated: Use NewVar or NewConst instead. func NewIdent(name string, t *exprpb.Type, v *exprpb.Constant) *exprpb.Decl { + return newIdent(name, t, v, "") +} + +func newIdent(name string, t *exprpb.Type, v *exprpb.Constant, desc string) *exprpb.Decl { return &exprpb.Decl{ Name: name, DeclKind: &exprpb.Decl_Ident{ Ident: &exprpb.Decl_IdentDecl{ Type: t, - Value: v}}} + Value: v, + Doc: desc}}} } // NewConst creates a constant identifier with a CEL constant literal value. func NewConst(name string, t *exprpb.Type, v *exprpb.Constant) *exprpb.Decl { - return NewIdent(name, t, v) + return newIdent(name, t, v, "") } // NewVar creates a variable identifier. func NewVar(name string, t *exprpb.Type) *exprpb.Decl { - return NewIdent(name, t, nil) + return newIdent(name, t, nil, "") +} + +// NewVarWithDoc creates a variable identifier with a type and a description string. +func NewVarWithDoc(name string, t *exprpb.Type, desc string) *exprpb.Decl { + return newIdent(name, t, nil, desc) } // NewInstanceOverload creates a instance function overload contract. // First element of argTypes is instance. -func NewInstanceOverload(id string, argTypes []*exprpb.Type, - resultType *exprpb.Type) *exprpb.Decl_FunctionDecl_Overload { +func NewInstanceOverload(id string, argTypes []*exprpb.Type, resultType *exprpb.Type) *exprpb.Decl_FunctionDecl_Overload { return &exprpb.Decl_FunctionDecl_Overload{ OverloadId: id, ResultType: resultType, @@ -154,8 +174,7 @@ func NewObjectType(typeName string) *exprpb.Type { // NewOverload creates a function overload declaration which contains a unique // overload id as well as the expected argument and result types. Overloads // must be aggregated within a Function declaration. -func NewOverload(id string, argTypes []*exprpb.Type, - resultType *exprpb.Type) *exprpb.Decl_FunctionDecl_Overload { +func NewOverload(id string, argTypes []*exprpb.Type, resultType *exprpb.Type) *exprpb.Decl_FunctionDecl_Overload { return &exprpb.Decl_FunctionDecl_Overload{ OverloadId: id, ResultType: resultType, @@ -231,7 +250,5 @@ func NewWrapperType(wrapped *exprpb.Type) *exprpb.Type { // TODO: return an error panic("Wrapped type must be a primitive") } - return &exprpb.Type{ - TypeKind: &exprpb.Type_Wrapper{ - Wrapper: primitive}} + return &exprpb.Type{TypeKind: &exprpb.Type_Wrapper{Wrapper: primitive}} } diff --git a/vendor/github.com/google/cel-go/checker/env.go b/vendor/github.com/google/cel-go/checker/env.go index d5ac05014ee4f..477918c485093 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. @@ -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/errors.go b/vendor/github.com/google/cel-go/checker/errors.go index 8b3bf0b8b6420..3535440baa4fe 100644 --- a/vendor/github.com/google/cel-go/checker/errors.go +++ b/vendor/github.com/google/cel-go/checker/errors.go @@ -45,6 +45,10 @@ func (e *typeErrors) notAComprehensionRange(id int64, l common.Location, t *type FormatCELType(t)) } +func (e *typeErrors) notAnOptionalFieldSelectionCall(id int64, l common.Location, err string) { + e.errs.ReportErrorAtID(id, l, "unsupported optional field selection: %s", err) +} + func (e *typeErrors) notAnOptionalFieldSelection(id int64, l common.Location, field ast.Expr) { e.errs.ReportErrorAtID(id, l, "unsupported optional field selection: %v", field) } diff --git a/vendor/github.com/google/cel-go/checker/options.go b/vendor/github.com/google/cel-go/checker/options.go index 0560c3813cf17..af714323baecf 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 @@ -40,3 +41,11 @@ func ValidatedDeclarations(env *Env) Option { 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 8bb73ddb6a297..9ae9832e15272 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" ) @@ -76,6 +78,7 @@ func (s *Scopes) AddIdent(decl *decls.VariableDecl) { // found. // Note: The search is performed from innermost to outermost. func (s *Scopes) FindIdent(name string) *decls.VariableDecl { + name = strings.TrimPrefix(name, ".") if ident, found := s.scopes.idents[name]; found { return ident } @@ -89,12 +92,33 @@ func (s *Scopes) FindIdent(name string) *decls.VariableDecl { // 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 } return nil } +// 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 + } + return scope.FindIdentInScope(name) +} + // SetFunction adds the function Decl to the current scope. // Note: Any previous entry for a function in the current scope with the same name is overwritten. func (s *Scopes) SetFunction(fn *decls.FunctionDecl) { @@ -105,6 +129,7 @@ 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 } diff --git a/vendor/github.com/google/cel-go/common/BUILD.bazel b/vendor/github.com/google/cel-go/common/BUILD.bazel index eef7f281be145..1b1b7914d65c0 100644 --- a/vendor/github.com/google/cel-go/common/BUILD.bazel +++ b/vendor/github.com/google/cel-go/common/BUILD.bazel @@ -9,6 +9,7 @@ go_library( name = "go_default_library", srcs = [ "cost.go", + "doc.go", "error.go", "errors.go", "location.go", @@ -25,6 +26,7 @@ go_test( name = "go_default_test", size = "small", srcs = [ + "doc_test.go", "errors_test.go", "source_test.go", ], 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 b807669d49c0a..c8f8f8a022e50 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,41 @@ 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) + PostOrderVisit(a.Expr(), visitor) + return visitor +} + // NewSourceInfo creates a simple SourceInfo object from an input common.Source value. func NewSourceInfo(src common.Source) *SourceInfo { var lineOffsets []int32 @@ -202,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, @@ -210,6 +252,7 @@ func CopySourceInfo(info *SourceInfo) *SourceInfo { baseCol: info.baseCol, offsetRanges: rangesCopy, macroCalls: callsCopy, + extensions: extCopy, } } @@ -223,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. @@ -358,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 } @@ -368,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 @@ -437,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 @@ -455,3 +599,84 @@ func (v *maxIDVisitor) VisitEntryExpr(e EntryExpr) { v.maxID = e.ID() } } + +type heightVisitor map[int64]int + +// VisitExpr computes the height of a given node as the max height of its children plus one. +// +// Identifiers and literals are treated as having a height of zero. +func (hv heightVisitor) VisitExpr(e Expr) { + // default includes IdentKind, LiteralKind + hv[e.ID()] = 0 + switch e.Kind() { + case SelectKind: + hv[e.ID()] = 1 + hv[e.AsSelect().Operand().ID()] + case CallKind: + c := e.AsCall() + height := hv.maxHeight(c.Args()...) + if c.IsMemberFunction() { + tHeight := hv[c.Target().ID()] + if tHeight > height { + height = tHeight + } + } + hv[e.ID()] = 1 + height + case ListKind: + l := e.AsList() + hv[e.ID()] = 1 + hv.maxHeight(l.Elements()...) + case MapKind: + m := e.AsMap() + hv[e.ID()] = 1 + hv.maxEntryHeight(m.Entries()...) + case StructKind: + s := e.AsStruct() + hv[e.ID()] = 1 + hv.maxEntryHeight(s.Fields()...) + case ComprehensionKind: + comp := e.AsComprehension() + hv[e.ID()] = 1 + hv.maxHeight(comp.IterRange(), comp.AccuInit(), comp.LoopCondition(), comp.LoopStep(), comp.Result()) + } +} + +// VisitEntryExpr computes the max height of a map or struct entry and associates the height with the entry id. +func (hv heightVisitor) VisitEntryExpr(e EntryExpr) { + hv[e.ID()] = 0 + switch e.Kind() { + case MapEntryKind: + me := e.AsMapEntry() + hv[e.ID()] = hv.maxHeight(me.Value(), me.Key()) + case StructFieldKind: + sf := e.AsStructField() + hv[e.ID()] = hv[sf.Value().ID()] + } +} + +func (hv heightVisitor) maxHeight(exprs ...Expr) int { + max := 0 + for _, e := range exprs { + h := hv[e.ID()] + if h > max { + max = h + } + } + return max +} + +func (hv heightVisitor) maxEntryHeight(entries ...EntryExpr) int { + max := 0 + for _, e := range entries { + h := hv[e.ID()] + if h > max { + max = h + } + } + 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 435d8f6547044..380f8c11810f2 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 d7a90fb7c385e..364edfa3af6bb 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 ( @@ -237,8 +260,13 @@ func visit(expr Expr, visitor Visitor, order visitOrder, depth, maxDepth int) { case StructKind: s := expr.AsStruct() for _, f := range s.Fields() { - visitor.VisitEntryExpr(f) + if order == preOrder { + visitor.VisitEntryExpr(f) + } visit(f.AsStructField().Value(), visitor, order, depth+1, maxDepth) + if order == postOrder { + visitor.VisitEntryExpr(f) + } } } if order == postOrder { 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 3097a3f785324..fcfcdfc3f49d5 100644 --- a/vendor/github.com/google/cel-go/common/containers/container.go +++ b/vendor/github.com/google/cel-go/common/containers/container.go @@ -63,9 +63,9 @@ func (c *Container) Extend(opts ...ContainerOption) (*Container, error) { } // Copy the name and aliases of the existing container. ext := &Container{name: c.Name()} - if len(c.aliasSet()) > 0 { - aliasSet := make(map[string]string, len(c.aliasSet())) - for k, v := range c.aliasSet() { + if len(c.AliasSet()) > 0 { + aliasSet := make(map[string]string, len(c.AliasSet())) + for k, v := range c.AliasSet() { aliasSet[k] = v } ext.aliases = aliasSet @@ -133,8 +133,8 @@ func (c *Container) ResolveCandidateNames(name string) []string { return append(candidates, name) } -// aliasSet returns the alias to fully-qualified name mapping stored in the container. -func (c *Container) aliasSet() map[string]string { +// AliasSet returns the alias to fully-qualified name mapping stored in the container. +func (c *Container) AliasSet() map[string]string { if c == nil || c.aliases == nil { return noAliases } @@ -160,7 +160,7 @@ func (c *Container) findAlias(name string) (string, bool) { simple = name[0:dot] qualifier = name[dot:] } - alias, found := c.aliasSet()[simple] + alias, found := c.AliasSet()[simple] if !found { return "", false } @@ -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,35 +236,36 @@ 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) } - aliasRef, found := c.aliasSet()[alias] + aliasRef, found := c.AliasSet()[alias] if found { return nil, fmt.Errorf( "%s collides with existing reference: name=%s, %s=%s, existing=%s", 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 75f5f0d6367e3..fbc847f0c1437 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/BUILD.bazel b/vendor/github.com/google/cel-go/common/decls/BUILD.bazel index 17791dce6a0db..bd3f9ae70e178 100644 --- a/vendor/github.com/google/cel-go/common/decls/BUILD.bazel +++ b/vendor/github.com/google/cel-go/common/decls/BUILD.bazel @@ -13,7 +13,9 @@ go_library( importpath = "github.com/google/cel-go/common/decls", deps = [ "//checker/decls:go_default_library", + "//common:go_default_library", "//common/functions:go_default_library", + "//common/operators:go_default_library", "//common/types:go_default_library", "//common/types/ref:go_default_library", "//common/types/traits:go_default_library", 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 bfeb52c515309..51cb689e58663 100644 --- a/vendor/github.com/google/cel-go/common/decls/decls.go +++ b/vendor/github.com/google/cel-go/common/decls/decls.go @@ -16,11 +16,14 @@ package decls import ( + "context" "fmt" "strings" chkdecls "github.com/google/cel-go/checker/decls" + "github.com/google/cel-go/common" "github.com/google/cel-go/common/functions" + "github.com/google/cel-go/common/operators" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" @@ -54,6 +57,7 @@ func NewFunction(name string, opts ...FunctionOpt) (*FunctionDecl, error) { // overload instances. type FunctionDecl struct { name string + doc string // overloads associated with the function name. overloads map[string]*OverloadDecl @@ -84,6 +88,26 @@ const ( declarationEnabled ) +// Documentation generates documentation about the Function and its overloads as a common.Doc object. +func (f *FunctionDecl) Documentation() *common.Doc { + if f == nil { + return nil + } + children := make([]*common.Doc, len(f.OverloadDecls())) + for i, o := range f.OverloadDecls() { + var examples []*common.Doc + for _, ex := range o.Examples() { + examples = append(examples, common.NewExampleDoc(ex)) + } + od := common.NewOverloadDoc(o.ID(), formatSignature(f.Name(), o), examples...) + children[i] = od + } + return common.NewFunctionDoc( + f.Name(), + f.Description(), + children...) +} + // Name returns the function name in human-readable terms, e.g. 'contains' of 'math.least' func (f *FunctionDecl) Name() string { if f == nil { @@ -92,9 +116,22 @@ func (f *FunctionDecl) Name() string { return f.name } +// Description provides an overview of the function's purpose. +// +// Usage examples should be included on specific overloads. +func (f *FunctionDecl) Description() string { + if f == nil { + return "" + } + return f.doc +} + // IsDeclarationDisabled indicates that the function implementation should be added to the dispatcher, but the // declaration should not be exposed for use in expressions. func (f *FunctionDecl) IsDeclarationDisabled() bool { + if f == nil { + return true + } return f.state == declarationDisabled } @@ -107,8 +144,8 @@ func (f *FunctionDecl) Merge(other *FunctionDecl) (*FunctionDecl, error) { if f == other { return f, nil } - if f.Name() != other.Name() { - return nil, fmt.Errorf("cannot merge unrelated functions. %s and %s", f.Name(), other.Name()) + if f == nil || other == nil || f.Name() != other.Name() { + return nil, fmt.Errorf("cannot merge unrelated functions. %q and %q", f.Name(), other.Name()) } merged := &FunctionDecl{ name: f.Name(), @@ -120,12 +157,17 @@ func (f *FunctionDecl) Merge(other *FunctionDecl) (*FunctionDecl, error) { disableTypeGuards: f.disableTypeGuards && other.disableTypeGuards, // default to the current functions declaration state. state: f.state, + doc: f.doc, } // If the other state indicates that the declaration should be explicitly enabled or // disabled, then update the merged state with the most recent value. if other.state != declarationStateUnset { merged.state = other.state } + // Allow for non-empty overrides of documentation + if len(other.doc) != 0 && f.doc != other.doc { + merged.doc = other.doc + } // baseline copy of the overloads and their ordinals copy(merged.overloadOrdinals, f.overloadOrdinals) for oID, o := range f.overloads { @@ -148,6 +190,70 @@ func (f *FunctionDecl) Merge(other *FunctionDecl) (*FunctionDecl, error) { return merged, nil } +// FunctionSubsetter subsets a function declaration or returns nil and false if the function +// subset was empty. +type FunctionSubsetter func(fn *FunctionDecl) (*FunctionDecl, bool) + +// OverloadSelector selects an overload associated with a given function when it returns true. +// +// Used in combination with the Subset method. +type OverloadSelector func(overload *OverloadDecl) bool + +// IncludeOverloads defines an OverloadSelector which allow-lists a set of overloads by their ids. +func IncludeOverloads(overloadIDs ...string) OverloadSelector { + return func(overload *OverloadDecl) bool { + for _, oID := range overloadIDs { + if overload.id == oID { + return true + } + } + return false + } +} + +// ExcludeOverloads defines an OverloadSelector which deny-lists a set of overloads by their ids. +func ExcludeOverloads(overloadIDs ...string) OverloadSelector { + return func(overload *OverloadDecl) bool { + for _, oID := range overloadIDs { + if overload.id == oID { + return false + } + } + return true + } +} + +// Subset returns a new function declaration which contains only the overloads with the specified IDs. +// If the subset function contains no overloads, then nil is returned to indicate the function is not +// functional. +func (f *FunctionDecl) Subset(selector OverloadSelector) *FunctionDecl { + if f == nil { + return nil + } + overloads := make(map[string]*OverloadDecl) + overloadOrdinals := make([]string, 0, len(f.overloadOrdinals)) + for _, oID := range f.overloadOrdinals { + overload := f.overloads[oID] + if selector(overload) { + overloads[oID] = overload + overloadOrdinals = append(overloadOrdinals, oID) + } + } + if len(overloads) == 0 { + return nil + } + subset := &FunctionDecl{ + name: f.Name(), + doc: f.doc, + overloads: overloads, + singleton: f.singleton, + disableTypeGuards: f.disableTypeGuards, + state: f.state, + overloadOrdinals: overloadOrdinals, + } + return subset +} + // AddOverload ensures that the new overload does not collide with an existing overload signature; // however, if the function signatures are identical, the implementation may be rewritten as its // difficult to compare functions by object identity. @@ -155,6 +261,9 @@ func (f *FunctionDecl) AddOverload(overload *OverloadDecl) error { if f == nil { return fmt.Errorf("nil function cannot add overload: %s", overload.ID()) } + if overload == nil { + return fmt.Errorf("cannot add nil overload to funciton: %s", f.Name()) + } for oID, o := range f.overloads { if oID != overload.ID() && o.SignatureOverlaps(overload) { return fmt.Errorf("overload signature collision in function %s: %s collides with %s", f.Name(), oID, overload.ID()) @@ -162,13 +271,20 @@ 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 } + // Allow redefinition of the doc string. + if len(overload.doc) != 0 && o.doc != overload.doc { + o.doc = overload.doc + } return nil } return fmt.Errorf("overload redefinition in function. %s: %s has multiple definitions", f.Name(), oID) } + if overload.HasLateBinding() != o.HasLateBinding() { + return fmt.Errorf("overload with late binding cannot be added to function %s: cannot mix late and non-late bindings", f.Name()) + } } f.overloadOrdinals = append(f.overloadOrdinals, overload.ID()) f.overloads[overload.ID()] = overload @@ -177,8 +293,9 @@ func (f *FunctionDecl) AddOverload(overload *OverloadDecl) error { // 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 []*OverloadDecl{} + return emptySet } overloads := make([]*OverloadDecl, 0, len(f.overloads)) for _, oID := range f.overloadOrdinals { @@ -187,21 +304,50 @@ func (f *FunctionDecl) OverloadDecls() []*OverloadDecl { return overloads } +// HasSingletonBinding indicates whether the function has a singleton binding definition. +func (f *FunctionDecl) HasSingletonBinding() bool { + if f == nil { + return false + } + return f.singleton != nil +} + +// HasLateBinding returns true if the function has late bindings. A function cannot mix late bindings with other bindings. +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 { + o := f.overloads[oID] + if o.HasLateBinding() { + return true + } + } + return false +} + // Bindings produces a set of function bindings, if any are defined. func (f *FunctionDecl) Bindings() ([]*functions.Overload, error) { + var emptySet []*functions.Overload if f == nil { - return []*functions.Overload{}, nil + return emptySet, nil } overloads := []*functions.Overload{} nonStrict := false + hasLateBinding := false for _, oID := range f.overloadOrdinals { o := f.overloads[oID] - if o.hasBinding() { + hasLateBinding = hasLateBinding || o.HasLateBinding() + 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(), } @@ -213,12 +359,16 @@ func (f *FunctionDecl) Bindings() ([]*functions.Overload, error) { if len(overloads) != 0 { return nil, fmt.Errorf("singleton function incompatible with specialized overloads: %s", f.Name()) } + if hasLateBinding { + return nil, fmt.Errorf("singleton function incompatible with late bindings: %s", f.Name()) + } overloads = []*functions.Overload{ { Operator: f.Name(), Unary: f.singleton.Unary, Binary: f.singleton.Binary, Function: f.singleton.Function, + Async: f.singleton.Async, OperandTrait: f.singleton.OperandTrait, }, } @@ -237,6 +387,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 @@ -298,6 +449,14 @@ func MaybeNoSuchOverload(funcName string, args ...ref.Val) ref.Val { // FunctionOpt defines a functional option for mutating a function declaration. type FunctionOpt func(*FunctionDecl) (*FunctionDecl, error) +// FunctionDocs configures documentation from a list of strings separated by newlines. +func FunctionDocs(docs ...string) FunctionOpt { + return func(fn *FunctionDecl) (*FunctionDecl, error) { + fn.doc = common.MultilineDescription(docs...) + return fn, nil + } +} + // DisableTypeGuards disables automatically generated function invocation guards on direct overload calls. // Type guards remain on during dynamic dispatch for parsed-only expressions. func DisableTypeGuards(value bool) FunctionOpt { @@ -387,6 +546,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. @@ -450,9 +633,13 @@ func newOverloadInternal(overloadID string, // implementation. type OverloadDecl struct { id string + doc string argTypes []*types.Type resultType *types.Type isMemberFunction bool + // hasLateBinding 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. + hasLateBinding bool // nonStrict indicates that the function will accept error and unknown arguments as inputs. nonStrict bool // operandTrait indicates whether the member argument should have a specific type-trait. @@ -467,6 +654,17 @@ 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. +func (o *OverloadDecl) Examples() []string { + var emptySet []string + if o == nil || len(o.doc) == 0 { + return emptySet + } + return common.ParseDescriptions(o.doc) } // ID mirrors the overload signature and provides a unique id which may be referenced within the type-checker @@ -508,6 +706,14 @@ func (o *OverloadDecl) IsNonStrict() bool { return o.nonStrict } +// HasLateBinding returns whether the overload has a binding which is not known at compile time. +func (o *OverloadDecl) HasLateBinding() bool { + if o == nil { + return false + } + return o.hasLateBinding || o.asyncOp != nil +} + // OperandTrait returns the trait mask of the first operand to the overload call, e.g. // `traits.Indexer` func (o *OverloadDecl) OperandTrait() int { @@ -576,9 +782,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. @@ -620,6 +826,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) && @@ -653,6 +875,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 } @@ -666,16 +890,27 @@ func matchOperandTrait(trait int, arg ref.Val) bool { // OverloadOpt is a functional option for configuring a function overload. type OverloadOpt func(*OverloadDecl) (*OverloadDecl, error) +// OverloadExamples configures example expressions for the overload. +func OverloadExamples(examples ...string) OverloadOpt { + return func(o *OverloadDecl) (*OverloadDecl, error) { + o.doc = common.MultilineDescription(examples...) + return o, nil + } +} + // UnaryBinding provides the implementation of a unary overload. The provided function is protected by a runtime // 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 { return nil, fmt.Errorf("unary function bound to non-unary overload: %s", o.ID()) } + if o.hasLateBinding { + return nil, fmt.Errorf("overload already has a late binding: %s", o.ID()) + } o.unaryOp = binding return o, nil } @@ -685,12 +920,15 @@ 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 { return nil, fmt.Errorf("binary function bound to non-binary overload: %s", o.ID()) } + if o.hasLateBinding { + return nil, fmt.Errorf("overload already has a late binding: %s", o.ID()) + } o.binaryOp = binding return o, nil } @@ -700,14 +938,63 @@ 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 { + return nil, fmt.Errorf("overload already has a late binding: %s", o.ID()) + } o.functionOp = binding return o, nil } } +// 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() { + return nil, fmt.Errorf("overload already has a binding: %s", o.ID()) + } + o.hasLateBinding = true + return o, nil + } +} + // 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. @@ -737,13 +1024,27 @@ func NewVariable(name string, t *types.Type) *VariableDecl { return &VariableDecl{name: name, varType: t} } +// NewVariableWithDoc creates a new variable declaration with usage documentation. +func NewVariableWithDoc(name string, t *types.Type, doc string) *VariableDecl { + return &VariableDecl{name: name, varType: t, doc: doc} +} + // VariableDecl defines a variable declaration which may optionally have a constant value. type VariableDecl struct { name string + doc string varType *types.Type value ref.Val } +// Documentation returns name, type, and description for the variable. +func (v *VariableDecl) Documentation() *common.Doc { + if v == nil { + return nil + } + return common.NewVariableDoc(v.Name(), describeCELType(v.Type()), v.Description()) +} + // Name returns the fully-qualified variable name func (v *VariableDecl) Name() string { if v == nil { @@ -752,6 +1053,16 @@ func (v *VariableDecl) Name() string { return v.name } +// Description returns the usage documentation for the variable, if set. +// +// Good usage instructions provide information about the valid formats, ranges, sizes for the variable type. +func (v *VariableDecl) Description() string { + if v == nil { + return "" + } + return v.doc +} + // Type returns the types.Type value associated with the variable. func (v *VariableDecl) Type() *types.Type { if v == nil { @@ -793,7 +1104,7 @@ func variableDeclToExprDecl(v *VariableDecl) (*exprpb.Decl, error) { if err != nil { return nil, err } - return chkdecls.NewVar(v.Name(), varType), nil + return chkdecls.NewVarWithDoc(v.Name(), varType, v.doc), nil } // FunctionDeclToExprDecl converts a go-native function declaration into a protobuf-typed function declaration. @@ -838,8 +1149,10 @@ func functionDeclToExprDecl(f *FunctionDecl) (*exprpb.Decl, error) { overloads[i] = chkdecls.NewParameterizedOverload(oID, argTypes, resultType, params) } } + doc := common.MultilineDescription(o.Examples()...) + overloads[i].Doc = doc } - return chkdecls.NewFunction(f.Name(), overloads...), nil + return chkdecls.NewFunctionWithDoc(f.Name(), f.Description(), overloads...), nil } func collectParamNames(paramNames map[string]struct{}, arg *types.Type) { @@ -851,6 +1164,60 @@ func collectParamNames(paramNames map[string]struct{}, arg *types.Type) { } } +func formatSignature(fnName string, o *OverloadDecl) string { + if opName, isOperator := operators.FindReverse(fnName); isOperator { + if opName == "" { + opName = fnName + } + return formatOperator(opName, o) + } + return formatCall(fnName, o) +} + +func formatOperator(opName string, o *OverloadDecl) string { + args := o.ArgTypes() + argTypes := make([]string, len(o.ArgTypes())) + for j, a := range args { + argTypes[j] = describeCELType(a) + } + ret := describeCELType(o.ResultType()) + switch len(args) { + case 1: + return fmt.Sprintf("%s%s -> %s", opName, argTypes[0], ret) + case 2: + if opName == operators.Index { + return fmt.Sprintf("%s[%s] -> %s", argTypes[0], argTypes[1], ret) + } + return fmt.Sprintf("%s %s %s -> %s", argTypes[0], opName, argTypes[1], ret) + default: + if opName == operators.Conditional { + return fmt.Sprint("bool ? : -> ") + } + return formatCall(opName, o) + } +} + +func formatCall(funcName string, o *OverloadDecl) string { + args := make([]string, len(o.ArgTypes())) + ret := describeCELType(o.ResultType()) + for j, a := range o.ArgTypes() { + args[j] = describeCELType(a) + } + if o.IsMemberFunction() { + target := args[0] + args = args[1:] + return fmt.Sprintf("%s.%s(%s) -> %s", target, funcName, strings.Join(args, ", "), ret) + } + return fmt.Sprintf("%s(%s) -> %s", funcName, strings.Join(args, ", "), ret) +} + +func describeCELType(t *types.Type) string { + if t.Kind() == types.TypeKind { + return "type" + } + return t.String() +} + var ( - emptyArgs = []*types.Type{} + emptyArgs []*types.Type ) diff --git a/vendor/github.com/google/cel-go/common/doc.go b/vendor/github.com/google/cel-go/common/doc.go index 5362fdfe4b3d4..c10742c284e16 100644 --- a/vendor/github.com/google/cel-go/common/doc.go +++ b/vendor/github.com/google/cel-go/common/doc.go @@ -15,3 +15,170 @@ // Package common defines types and utilities common to expression parsing, // checking, and interpretation package common + +import ( + "strings" + "unicode" +) + +// DocKind indicates the type of documentation element. +type DocKind int + +const ( + // DocEnv represents environment variable documentation. + DocEnv DocKind = iota + 1 + // DocFunction represents function documentation. + DocFunction + // DocOverload represents function overload documentation. + DocOverload + // DocVariable represents variable documentation. + DocVariable + // DocMacro represents macro documentation. + 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 +// a variable, function, macro, or example. +type Doc struct { + // Kind specifies the type of documentation element (e.g., Function, Variable). + Kind DocKind + + // Name is the identifier of the documented element (e.g., function name, variable name). + Name string + + // Type is the data type associated with the element, primarily used for variables. + Type string + + // Signature represents the function or overload signature. + Signature string + + // Description holds the textual description of the element, potentially spanning multiple lines. + Description string + + // Children holds nested documentation elements, such as overloads for a function + // or examples for a function/macro. + Children []*Doc +} + +// MultilineDescription combines multiple lines into a newline separated string. +func MultilineDescription(lines ...string) string { + return strings.Join(lines, "\n") +} + +// ParseDescription takes a single string containing newline characters and splits +// it into a multiline description. All empty lines will be skipped. +// +// Returns an empty string if the input string is empty. +func ParseDescription(doc string) string { + var lines []string + if len(doc) != 0 { + // Split the input string by newline characters. + for _, line := range strings.Split(doc, "\n") { + l := strings.TrimRightFunc(line, unicode.IsSpace) + if len(l) == 0 { + continue + } + lines = append(lines, l) + } + } + // Return an empty slice if the input is empty. + return MultilineDescription(lines...) +} + +// ParseDescriptions splits a documentation string into multiple multi-line description +// sections, using blank lines as delimiters. +func ParseDescriptions(doc string) []string { + var examples []string + if len(doc) != 0 { + lines := strings.Split(doc, "\n") + lineStart := 0 + for i, l := range lines { + // Trim trailing whitespace to identify effectively blank lines. + l = strings.TrimRightFunc(l, unicode.IsSpace) + // If a line is blank, it marks the end of the current section. + if len(l) == 0 { + // Start the next section after the blank line. + ex := lines[lineStart:i] + if len(ex) != 0 { + examples = append(examples, MultilineDescription(ex...)) + } + lineStart = i + 1 + } + } + // Append the last section if it wasn't terminated by a blank line. + if lineStart < len(lines) { + examples = append(examples, MultilineDescription(lines[lineStart:]...)) + } + } + return examples +} + +// NewVariableDoc creates a new Doc struct specifically for documenting a variable. +func NewVariableDoc(name, celType, description string) *Doc { + return &Doc{ + Kind: DocVariable, + Name: name, + Type: celType, + Description: ParseDescription(description), + } +} + +// NewFunctionDoc creates a new Doc struct for documenting a function. +func NewFunctionDoc(name, description string, overloads ...*Doc) *Doc { + return &Doc{ + Kind: DocFunction, + Name: name, + Description: ParseDescription(description), + Children: overloads, + } +} + +// NewOverloadDoc creates a new Doc struct for a function example. +func NewOverloadDoc(id, signature string, examples ...*Doc) *Doc { + return &Doc{ + Kind: DocOverload, + Name: id, + Signature: signature, + Children: examples, + } +} + +// NewMacroDoc creates a new Doc struct for documenting a macro. +func NewMacroDoc(name, description string, examples ...*Doc) *Doc { + return &Doc{ + Kind: DocMacro, + Name: name, + Description: ParseDescription(description), + Children: examples, + } +} + +// NewExampleDoc creates a new Doc struct specifically for holding an example. +func NewExampleDoc(ex string) *Doc { + return &Doc{ + Kind: DocExample, + Description: ex, + } +} + +// 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 + // with text formatting. + Documentation() *Doc +} diff --git a/vendor/github.com/google/cel-go/common/env/BUILD.bazel b/vendor/github.com/google/cel-go/common/env/BUILD.bazel new file mode 100644 index 0000000000000..261da924d4350 --- /dev/null +++ b/vendor/github.com/google/cel-go/common/env/BUILD.bazel @@ -0,0 +1,54 @@ +# 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 +# +# https://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. + +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +package( + default_visibility = ["//visibility:public"], + licenses = ["notice"], # Apache 2.0 +) + +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", + ], +) + +go_test( + name = "go_default_test", + size = "small", + srcs = [ + "env_test.go", + "io_test.go", + ], + data = glob(["testdata/**"]), + embed = [":go_default_library"], + deps = [ + "//common/decls:go_default_library", + "//common/operators:go_default_library", + "//common/overloads:go_default_library", + "//common/types: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 new file mode 100644 index 0000000000000..936036ed21ca9 --- /dev/null +++ b/vendor/github.com/google/cel-go/common/env/env.go @@ -0,0 +1,1002 @@ +// 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 env provides a representation of a CEL environment. +package env + +import ( + "errors" + "fmt" + "math" + "strconv" + "strings" + + "github.com/google/cel-go/common/decls" + "github.com/google/cel-go/common/types" +) + +// NewConfig creates an instance of a YAML serializable CEL environment configuration. +func NewConfig(name string) *Config { + return &Config{ + Name: name, + } +} + +// Config represents a serializable form of the CEL environment configuration. +// +// Note: custom validations, feature flags, and performance tuning parameters are not (yet) +// considered part of the core CEL environment configuration and should be managed separately +// until a common convention for such settings is developed. +type Config struct { + Name string `yaml:"name,omitempty"` + Description string `yaml:"description,omitempty"` + Container string `yaml:"container,omitempty"` + Imports []*Import `yaml:"imports,omitempty"` + StdLib *LibrarySubset `yaml:"stdlib,omitempty"` + Extensions []*Extension `yaml:"extensions,omitempty"` + ContextVariable *ContextVariable `yaml:"context_variable,omitempty"` + Variables []*Variable `yaml:"variables,omitempty"` + 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. +func (c *Config) Validate() error { + if c == nil { + return nil + } + var errs []error + for _, imp := range c.Imports { + if err := imp.Validate(); err != nil { + errs = append(errs, err) + } + } + if err := c.StdLib.Validate(); err != nil { + errs = append(errs, err) + } + for _, ext := range c.Extensions { + if err := ext.Validate(); err != nil { + errs = append(errs, err) + } + } + if err := c.ContextVariable.Validate(); err != nil { + errs = append(errs, err) + } + if c.ContextVariable != nil && len(c.Variables) != 0 { + errs = append(errs, errors.New("invalid config: either context variable or variables may be set, but not both")) + } + for _, v := range c.Variables { + if err := v.Validate(); err != nil { + errs = append(errs, err) + } + } + for _, fn := range c.Functions { + if err := fn.Validate(); err != nil { + errs = append(errs, err) + } + } + for _, feat := range c.Features { + if err := feat.Validate(); err != nil { + 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) + } + } + return errors.Join(errs...) +} + +// SetContainer configures the container name for this configuration. +func (c *Config) SetContainer(container string) *Config { + c.Container = container + return c +} + +// AddVariableDecls adds one or more variables to the config, converting them to serializable values first. +// +// VariableDecl inputs are expected to be well-formed. +func (c *Config) AddVariableDecls(vars ...*decls.VariableDecl) *Config { + convVars := make([]*Variable, len(vars)) + for i, v := range vars { + if v == nil { + continue + } + cv := NewVariable(v.Name(), SerializeTypeDesc(v.Type())) + cv.Description = v.Description() + convVars[i] = cv + } + return c.AddVariables(convVars...) +} + +// AddVariables adds one or more variables to the config. +func (c *Config) AddVariables(vars ...*Variable) *Config { + c.Variables = append(c.Variables, vars...) + return c +} + +// SetContextVariable configures the ContextVariable for this configuration. +func (c *Config) SetContextVariable(ctx *ContextVariable) *Config { + c.ContextVariable = ctx + return c +} + +// AddFunctionDecls adds one or more functions to the config, converting them to serializable values first. +// +// FunctionDecl inputs are expected to be well-formed. +func (c *Config) AddFunctionDecls(funcs ...*decls.FunctionDecl) *Config { + convFuncs := make([]*Function, len(funcs)) + for i, fn := range funcs { + if fn == nil { + continue + } + overloads := make([]*Overload, 0, len(fn.OverloadDecls())) + for _, o := range fn.OverloadDecls() { + overloadID := o.ID() + args := make([]*TypeDesc, 0, len(o.ArgTypes())) + for _, a := range o.ArgTypes() { + args = append(args, SerializeTypeDesc(a)) + } + ret := SerializeTypeDesc(o.ResultType()) + var overload *Overload + if o.IsMemberFunction() { + overload = NewMemberOverload(overloadID, args[0], args[1:], ret) + } else { + overload = NewOverload(overloadID, args, ret) + } + exampleCount := len(o.Examples()) + if exampleCount > 0 { + overload.Examples = o.Examples() + } + overloads = append(overloads, overload) + } + cf := NewFunction(fn.Name(), overloads...) + cf.Description = fn.Description() + convFuncs[i] = cf + } + return c.AddFunctions(convFuncs...) +} + +// AddFunctions adds one or more functions to the config. +func (c *Config) AddFunctions(funcs ...*Function) *Config { + c.Functions = append(c.Functions, funcs...) + return c +} + +// SetStdLib configures the LibrarySubset for the standard library. +func (c *Config) SetStdLib(subset *LibrarySubset) *Config { + c.StdLib = subset + return c +} + +// AddImports appends a set of imports to the config. +func (c *Config) AddImports(imps ...*Import) *Config { + c.Imports = append(c.Imports, imps...) + return c +} + +// AddExtensions appends a set of extensions to the config. +func (c *Config) AddExtensions(exts ...*Extension) *Config { + c.Extensions = append(c.Extensions, exts...) + return c +} + +// AddValidators appends one or more validators to the config. +func (c *Config) AddValidators(vals ...*Validator) *Config { + c.Validators = append(c.Validators, vals...) + return c +} + +// AddFeatures appends one or more features to the config. +func (c *Config) AddFeatures(feats ...*Feature) *Config { + c.Features = append(c.Features, feats...) + 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} +} + +// Import represents a type name that will be appreviated by its simple name using +// the cel.Abbrevs() option. +type Import struct { + Name string `yaml:"name"` +} + +// Validate validates the import configuration is well-formed. +func (imp *Import) Validate() error { + if imp == nil { + return errors.New("invalid import: nil") + } + if imp.Name == "" { + return errors.New("invalid import: missing type name") + } + return nil +} + +// NewVariable returns a serializable variable from a name and type definition +func NewVariable(name string, t *TypeDesc) *Variable { + return NewVariableWithDoc(name, t, "") +} + +// NewVariableWithDoc returns a serializable variable from a name, type definition, and doc string. +func NewVariableWithDoc(name string, t *TypeDesc, doc string) *Variable { + return &Variable{Name: name, TypeDesc: t, Description: doc} +} + +// Variable represents a typed variable declaration which will be published via the +// cel.VariableDecls() option. +type Variable struct { + Name string `yaml:"name"` + Description string `yaml:"description,omitempty"` + + // Type represents the type declaration for the variable. + // + // 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. + *TypeDesc `yaml:",inline"` +} + +// Validate validates the variable configuration is well-formed. +func (v *Variable) Validate() error { + if v == nil { + return errors.New("invalid variable: nil") + } + if v.Name == "" { + return errors.New("invalid variable: missing variable name") + } + 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 +} + +// GetType returns the variable type description. +// +// Note, if both the embedded TypeDesc and the field Type are non-nil, the embedded TypeDesc will +// take precedence. +func (v *Variable) GetType() *TypeDesc { + if v == nil { + return nil + } + if v.TypeDesc != nil { + return v.TypeDesc + } + if v.Type != nil { + return v.Type + } + return nil +} + +// AsCELVariable converts the serializable form of the Variable into a CEL environment declaration. +func (v *Variable) AsCELVariable(tp types.Provider) (*decls.VariableDecl, error) { + if err := v.Validate(); err != nil { + return nil, err + } + t, err := v.GetType().AsCELType(tp) + if err != nil { + return nil, fmt.Errorf("invalid variable %q: %w", v.Name, err) + } + return decls.NewVariableWithDoc(v.Name, t, v.Description), nil +} + +// NewContextVariable returns a serializable context variable with a specific type name. +func NewContextVariable(typeName string) *ContextVariable { + return &ContextVariable{TypeName: typeName} +} + +// ContextVariable represents a structured message whose fields are to be treated as the top-level +// variable identifiers within CEL expressions. +type ContextVariable struct { + // TypeName represents the fully qualified typename of the context variable. + // Currently, only protobuf types are supported. + TypeName string `yaml:"type_name"` +} + +// Validate validates the context-variable configuration is well-formed. +func (ctx *ContextVariable) Validate() error { + if ctx == nil { + return nil + } + if ctx.TypeName == "" { + return errors.New("invalid context variable: missing type name") + } + return nil +} + +// NewFunction creates a serializable function and overload set. +func NewFunction(name string, overloads ...*Overload) *Function { + return &Function{Name: name, Overloads: overloads} +} + +// NewFunctionWithDoc creates a serializable function and overload set. +func NewFunctionWithDoc(name, doc string, overloads ...*Overload) *Function { + return &Function{Name: name, Description: doc, Overloads: overloads} +} + +// Function represents the serializable format of a function and its overloads. +type Function struct { + Name string `yaml:"name"` + Description string `yaml:"description,omitempty"` + Overloads []*Overload `yaml:"overloads,omitempty"` +} + +// Validate validates the function configuration is well-formed. +func (fn *Function) Validate() error { + if fn == nil { + return errors.New("invalid function: nil") + } + if fn.Name == "" { + return errors.New("invalid function: missing function name") + } + if len(fn.Overloads) == 0 { + return fmt.Errorf("invalid function %q: missing overloads", fn.Name) + } + var errs []error + for _, o := range fn.Overloads { + if err := o.Validate(); err != nil { + errs = append(errs, fmt.Errorf("invalid function %q: %w", fn.Name, err)) + } + } + return errors.Join(errs...) +} + +// AsCELFunction converts the serializable form of the Function into CEL environment declaration. +func (fn *Function) AsCELFunction(tp types.Provider) (*decls.FunctionDecl, error) { + if err := fn.Validate(); err != nil { + return nil, err + } + opts := make([]decls.FunctionOpt, 0, len(fn.Overloads)+1) + for _, o := range fn.Overloads { + opt, err := o.AsFunctionOption(tp) + opts = append(opts, opt) + if err != nil { + return nil, fmt.Errorf("invalid function %q: %w", fn.Name, err) + } + } + if len(fn.Description) != 0 { + opts = append(opts, decls.FunctionDocs(fn.Description)) + } + return decls.NewFunction(fn.Name, opts...) +} + +// NewOverload returns a new serializable representation of a global overload. +func NewOverload(id string, args []*TypeDesc, ret *TypeDesc, examples ...string) *Overload { + return &Overload{ID: id, Args: args, Return: ret, Examples: examples} +} + +// NewMemberOverload returns a new serializable representation of a member (receiver) overload. +func NewMemberOverload(id string, target *TypeDesc, args []*TypeDesc, ret *TypeDesc, examples ...string) *Overload { + return &Overload{ID: id, Target: target, Args: args, Return: ret, Examples: examples} +} + +// Overload represents the serializable format of a function overload. +type Overload struct { + ID string `yaml:"id"` + Examples []string `yaml:"examples,omitempty"` + Target *TypeDesc `yaml:"target,omitempty"` + Args []*TypeDesc `yaml:"args,omitempty"` + Return *TypeDesc `yaml:"return,omitempty"` +} + +// Validate validates the overload configuration is well-formed. +func (od *Overload) Validate() error { + if od == nil { + return errors.New("invalid overload: nil") + } + if od.ID == "" { + return errors.New("invalid overload: missing overload id") + } + var errs []error + if od.Target != nil { + if err := od.Target.Validate(); err != nil { + errs = append(errs, fmt.Errorf("invalid overload %q target: %w", od.ID, err)) + } + } + for i, arg := range od.Args { + if err := arg.Validate(); err != nil { + errs = append(errs, fmt.Errorf("invalid overload %q arg[%d]: %w", od.ID, i, err)) + } + } + if err := od.Return.Validate(); err != nil { + errs = append(errs, fmt.Errorf("invalid overload %q return: %w", od.ID, err)) + } + return errors.Join(errs...) +} + +// AsFunctionOption converts the serializable form of the Overload into a function declaration option. +func (od *Overload) AsFunctionOption(tp types.Provider) (decls.FunctionOpt, error) { + if err := od.Validate(); err != nil { + return nil, err + } + args := make([]*types.Type, len(od.Args)) + var err error + var errs []error + for i, a := range od.Args { + args[i], err = a.AsCELType(tp) + if err != nil { + errs = append(errs, err) + } + } + result, err := od.Return.AsCELType(tp) + if err != nil { + errs = append(errs, err) + } + if od.Target != nil { + t, err := od.Target.AsCELType(tp) + if err != nil { + return nil, errors.Join(append(errs, err)...) + } + args = append([]*types.Type{t}, args...) + return decls.MemberOverload(od.ID, args, result), nil + } + if len(errs) != 0 { + return nil, errors.Join(errs...) + } + return decls.Overload(od.ID, args, result, decls.OverloadExamples(od.Examples...)), nil +} + +// NewExtension creates a serializable Extension from a name and version string. +func NewExtension(name string, version uint32) *Extension { + versionString := "latest" + if version < math.MaxUint32 { + versionString = strconv.FormatUint(uint64(version), 10) + } + return &Extension{ + Name: name, + Version: versionString, + } +} + +// Extension represents a named and optionally versioned extension library configured in the environment. +type Extension struct { + // Name is either the LibraryName() or some short-hand simple identifier which is understood by the config-handler. + Name string `yaml:"name"` + + // Version may either be an unsigned long value or the string 'latest'. If empty, the value is treated as '0'. + Version string `yaml:"version,omitempty"` +} + +// Validate validates the extension configuration is well-formed. +func (e *Extension) Validate() error { + _, err := e.VersionNumber() + return err +} + +// VersionNumber returns the parsed version string, or an error if the version cannot be parsed. +func (e *Extension) VersionNumber() (uint32, error) { + if e == nil { + return 0, fmt.Errorf("invalid extension: nil") + } + if e.Name == "" { + return 0, fmt.Errorf("invalid extension: missing name") + } + if e.Version == "latest" { + return math.MaxUint32, nil + } + if e.Version == "" { + return 0, nil + } + ver, err := strconv.ParseUint(e.Version, 10, 32) + if err != nil { + return 0, fmt.Errorf("invalid extension %q version: %w", e.Name, err) + } + return uint32(ver), nil +} + +// NewLibrarySubset returns an empty library subsetting config which permits all library features. +func NewLibrarySubset() *LibrarySubset { + return &LibrarySubset{} +} + +// LibrarySubset indicates a subset of the macros and function supported by a subsettable library. +type LibrarySubset struct { + // Disabled indicates whether the library has been disabled, typically only used for + // default-enabled libraries like stdlib. + Disabled bool `yaml:"disabled,omitempty"` + + // DisableMacros disables macros for the given library. + DisableMacros bool `yaml:"disable_macros,omitempty"` + + // IncludeMacros specifies a set of macro function names to include in the subset. + IncludeMacros []string `yaml:"include_macros,omitempty"` + + // ExcludeMacros specifies a set of macro function names to exclude from the subset. + // Note: if IncludeMacros is non-empty, then ExcludeFunctions is ignored. + ExcludeMacros []string `yaml:"exclude_macros,omitempty"` + + // IncludeFunctions specifies a set of functions to include in the subset. + // + // Note: the overloads specified in the subset need only specify their ID. + // Note: if IncludeFunctions is non-empty, then ExcludeFunctions is ignored. + IncludeFunctions []*Function `yaml:"include_functions,omitempty"` + + // ExcludeFunctions specifies the set of functions to exclude from the subset. + // + // Note: the overloads specified in the subset need only specify their ID. + ExcludeFunctions []*Function `yaml:"exclude_functions,omitempty"` +} + +// Validate validates the library configuration is well-formed. +// +// For example, setting both the IncludeMacros and ExcludeMacros together could be confusing +// and create a broken expectation, likewise for IncludeFunctions and ExcludeFunctions. +func (lib *LibrarySubset) Validate() error { + if lib == nil { + return nil + } + var errs []error + if len(lib.IncludeMacros) != 0 && len(lib.ExcludeMacros) != 0 { + errs = append(errs, errors.New("invalid subset: cannot both include and exclude macros")) + } + if len(lib.IncludeFunctions) != 0 && len(lib.ExcludeFunctions) != 0 { + errs = append(errs, errors.New("invalid subset: cannot both include and exclude functions")) + } + return errors.Join(errs...) +} + +// SubsetFunction produces a function declaration which matches the supported subset, or nil +// if the function is not supported by the LibrarySubset. +// +// For IncludeFunctions, if the function does not specify a set of overloads to include, the +// whole function definition is included. If overloads are set, then a new function which +// includes only the specified overloads is produced. +// +// For ExcludeFunctions, if the function does not specify a set of overloads to exclude, the +// whole function definition is excluded. If overloads are set, then a new function which +// includes only the permitted overloads is produced. +func (lib *LibrarySubset) SubsetFunction(fn *decls.FunctionDecl) (*decls.FunctionDecl, bool) { + // When lib is null, it should indicate that all values are included in the subset. + if lib == nil { + return fn, true + } + if lib.Disabled { + return nil, false + } + if len(lib.IncludeFunctions) != 0 { + for _, include := range lib.IncludeFunctions { + if include.Name != fn.Name() { + continue + } + if len(include.Overloads) == 0 { + return fn, true + } + overloadIDs := make([]string, len(include.Overloads)) + for i, o := range include.Overloads { + overloadIDs[i] = o.ID + } + return fn.Subset(decls.IncludeOverloads(overloadIDs...)), true + } + return nil, false + } + if len(lib.ExcludeFunctions) != 0 { + for _, exclude := range lib.ExcludeFunctions { + if exclude.Name != fn.Name() { + continue + } + if len(exclude.Overloads) == 0 { + return nil, false + } + overloadIDs := make([]string, len(exclude.Overloads)) + for i, o := range exclude.Overloads { + overloadIDs[i] = o.ID + } + return fn.Subset(decls.ExcludeOverloads(overloadIDs...)), true + } + return fn, true + } + return fn, true +} + +// SubsetMacro indicates whether the macro function should be included in the library subset. +func (lib *LibrarySubset) SubsetMacro(macroFunction string) bool { + // When lib is null, it should indicate that all values are included in the subset. + if lib == nil { + return true + } + if lib.Disabled || lib.DisableMacros { + return false + } + if len(lib.IncludeMacros) != 0 { + for _, name := range lib.IncludeMacros { + if name == macroFunction { + return true + } + } + return false + } + if len(lib.ExcludeMacros) != 0 { + for _, name := range lib.ExcludeMacros { + if name == macroFunction { + return false + } + } + return true + } + return true +} + +// SetDisabled disables or enables the library. +func (lib *LibrarySubset) SetDisabled(value bool) *LibrarySubset { + lib.Disabled = value + return lib +} + +// SetDisableMacros disables the macros for the library. +func (lib *LibrarySubset) SetDisableMacros(value bool) *LibrarySubset { + lib.DisableMacros = value + return lib +} + +// AddIncludedMacros allow-lists one or more macros by function name. +// +// Note, this option will override any excluded macros. +func (lib *LibrarySubset) AddIncludedMacros(macros ...string) *LibrarySubset { + lib.IncludeMacros = append(lib.IncludeMacros, macros...) + return lib +} + +// AddExcludedMacros deny-lists one or more macros by function name. +func (lib *LibrarySubset) AddExcludedMacros(macros ...string) *LibrarySubset { + lib.ExcludeMacros = append(lib.ExcludeMacros, macros...) + return lib +} + +// AddIncludedFunctions allow-lists one or more functions from the subset. +// +// Note, this option will override any excluded functions. +func (lib *LibrarySubset) AddIncludedFunctions(funcs ...*Function) *LibrarySubset { + lib.IncludeFunctions = append(lib.IncludeFunctions, funcs...) + return lib +} + +// AddExcludedFunctions deny-lists one or more functions from the subset. +func (lib *LibrarySubset) AddExcludedFunctions(funcs ...*Function) *LibrarySubset { + lib.ExcludeFunctions = append(lib.ExcludeFunctions, funcs...) + return lib +} + +// NewValidator returns a named Validator instance. +func NewValidator(name string) *Validator { + return &Validator{Name: name} +} + +// Validator represents a named validator with an optional map-based configuration object. +// +// Note: the map-keys must directly correspond to the internal representation of the original +// validator, and should only use primitive scalar types as values at this time. +type Validator struct { + Name string `yaml:"name"` + Config map[string]any `yaml:"config,omitempty"` +} + +// Validate validates the configuration of the validator object. +func (v *Validator) Validate() error { + if v == nil { + return errors.New("invalid validator: nil") + } + if v.Name == "" { + return errors.New("invalid validator: missing name") + } + return nil +} + +// SetConfig sets the set of map key-value pairs associated with this validator's configuration. +func (v *Validator) SetConfig(config map[string]any) *Validator { + v.Config = config + return v +} + +// ConfigValue retrieves the value associated with the config key name, if one exists. +func (v *Validator) ConfigValue(name string) (any, bool) { + if v == nil { + return nil, false + } + value, found := v.Config[name] + return value, found +} + +// NewFeature creates a new feature flag with a boolean enablement flag. +func NewFeature(name string, enabled bool) *Feature { + return &Feature{Name: name, Enabled: enabled} +} + +// Feature represents a named boolean feature flag supported by CEL. +type Feature struct { + Name string `yaml:"name"` + Enabled bool `yaml:"enabled"` +} + +// Validate validates whether the feature is well-configured. +func (feat *Feature) Validate() error { + if feat == nil { + return errors.New("invalid feature: nil") + } + if feat.Name == "" { + return errors.New("invalid feature: missing name") + } + 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} +} + +// NewTypeParam describe a type-param type. +func NewTypeParam(paramName string) *TypeDesc { + return &TypeDesc{TypeName: paramName, IsTypeParam: true} +} + +// TypeDesc represents the serializable format of a CEL *types.Type value. +type TypeDesc struct { + TypeName string `yaml:"type_name"` + Params []*TypeDesc `yaml:"params,omitempty"` + IsTypeParam bool `yaml:"is_type_param,omitempty"` +} + +// String implements the strings.Stringer interface method. +func (td *TypeDesc) String() string { + ps := make([]string, len(td.Params)) + for i, p := range td.Params { + ps[i] = p.String() + } + typeName := td.TypeName + if len(ps) != 0 { + typeName = fmt.Sprintf("%s(%s)", typeName, strings.Join(ps, ",")) + } + return typeName +} + +// Validate validates the type configuration is well-formed. +func (td *TypeDesc) Validate() error { + if td == nil { + return errors.New("invalid type: nil") + } + if td.TypeName == "" { + return errors.New("invalid type: missing type name") + } + if td.IsTypeParam && len(td.Params) != 0 { + return errors.New("invalid type: param type cannot have parameters") + } + switch td.TypeName { + case "list": + if len(td.Params) != 1 { + return fmt.Errorf("invalid type: list expects 1 parameter, got %d", len(td.Params)) + } + return td.Params[0].Validate() + case "map": + if len(td.Params) != 2 { + return fmt.Errorf("invalid type: map expects 2 parameters, got %d", len(td.Params)) + } + if err := td.Params[0].Validate(); err != nil { + return err + } + if err := td.Params[1].Validate(); err != nil { + return err + } + case "optional_type": + if len(td.Params) != 1 { + 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() + if err != nil { + return nil, err + } + 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 { + return nil, err + } + vt, err := td.Params[1].AsCELType(tp) + if err != nil { + return nil, err + } + return types.NewMapType(kt, vt), nil + case "list": + et, err := td.Params[0].AsCELType(tp) + if err != nil { + return nil, err + } + return types.NewListType(et), nil + case "optional_type": + et, err := td.Params[0].AsCELType(tp) + if err != nil { + 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 + } + if msgType, found := tp.FindStructType(td.TypeName); found { + // First parameter is the type name. + return msgType.Parameters()[0], nil + } + t, found := tp.FindIdent(td.TypeName) + if !found { + return nil, fmt.Errorf("undefined type name: %q", td.TypeName) + } + _, ok := t.(*types.Type) + if ok && len(td.Params) == 0 { + return t.(*types.Type), nil + } + params := make([]*types.Type, len(td.Params)) + for i, p := range td.Params { + params[i], err = p.AsCELType(tp) + if err != nil { + return nil, err + } + } + return types.NewOpaqueType(td.TypeName, params...), nil + } +} + +// SerializeTypeDesc converts a CEL native *types.Type to a serializable TypeDesc. +func SerializeTypeDesc(t *types.Type) *TypeDesc { + typeName := t.TypeName() + if t.Kind() == types.TypeParamKind { + return NewTypeParam(typeName) + } + if t != types.NullType && t.IsAssignableType(types.NullType) { + if wrapperTypeName, found := wrapperTypes[t.Kind()]; found { + return NewTypeDesc(wrapperTypeName) + } + } + var params []*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...) +} + +var wrapperTypes = map[types.Kind]string{ + types.BoolKind: "google.protobuf.BoolValue", + types.BytesKind: "google.protobuf.BytesValue", + types.DoubleKind: "google.protobuf.DoubleValue", + types.IntKind: "google.protobuf.Int64Value", + types.StringKind: "google.protobuf.StringValue", + types.UintKind: "google.protobuf.UInt64Value", +} 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 0000000000000..ec126f9ce67e8 --- /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 67f4a5944e15f..0c00781d9ce66 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 9d50f4367bf2e..0e3a7244956ed 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 021198224dc81..58dd33e13e8b6 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 ec79cb5454e51..9187e9b5c38d4 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/BUILD.bazel b/vendor/github.com/google/cel-go/common/stdlib/BUILD.bazel index b55f4521562c2..124dbea8195a7 100644 --- a/vendor/github.com/google/cel-go/common/stdlib/BUILD.bazel +++ b/vendor/github.com/google/cel-go/common/stdlib/BUILD.bazel @@ -12,6 +12,7 @@ go_library( ], importpath = "github.com/google/cel-go/common/stdlib", deps = [ + "//common:go_default_library", "//common/decls:go_default_library", "//common/functions:go_default_library", "//common/operators:go_default_library", 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 1550c1786387e..d2313bef1bd4b 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,12 @@ package stdlib import ( + "math" + "strconv" + "strings" + "time" + + "github.com/google/cel-go/common" "github.com/google/cel-go/common/decls" "github.com/google/cel-go/common/functions" "github.com/google/cel-go/common/operators" @@ -28,6 +34,7 @@ import ( var ( stdFunctions []*decls.FunctionDecl stdTypes []*decls.VariableDecl + utcTZ = types.String("UTC") ) func init() { @@ -55,19 +62,49 @@ func init() { // Logical operators. Special-cased within the interpreter. // Note, the singleton binding prevents extensions from overriding the operator behavior. function(operators.Conditional, + decls.FunctionDocs( + `The ternary operator tests a boolean predicate and returns the left-hand side `+ + `(truthy) expression if true, or the right-hand side (falsy) expression if false`), decls.Overload(overloads.Conditional, argTypes(types.BoolType, paramA, paramA), paramA, - decls.OverloadIsNonStrict()), + decls.OverloadIsNonStrict(), + decls.OverloadExamples( + `'hello'.contains('lo') ? 'hi' : 'bye' // 'hi'`, + `32 % 3 == 0 ? 'divisible' : 'not divisible' // 'not divisible'`)), decls.SingletonFunctionBinding(noFunctionOverrides)), + function(operators.LogicalAnd, + decls.FunctionDocs( + `logically AND two boolean values. Errors and unknown values`, + `are valid inputs and will not halt evaluation.`), decls.Overload(overloads.LogicalAnd, argTypes(types.BoolType, types.BoolType), types.BoolType, - decls.OverloadIsNonStrict()), + decls.OverloadIsNonStrict(), + decls.OverloadExamples( + `true && true // true`, + `true && false // false`, + `error && true // error`, + `error && false // false`)), decls.SingletonBinaryBinding(noBinaryOverrides)), + function(operators.LogicalOr, + decls.FunctionDocs( + `logically OR two boolean values. Errors and unknown values`, + `are valid inputs and will not halt evaluation.`), decls.Overload(overloads.LogicalOr, argTypes(types.BoolType, types.BoolType), types.BoolType, - decls.OverloadIsNonStrict()), + decls.OverloadIsNonStrict(), + decls.OverloadExamples( + `true || false // true`, + `false || false // false`, + `error || true // true`, + `error || error // true`)), decls.SingletonBinaryBinding(noBinaryOverrides)), + function(operators.LogicalNot, - decls.Overload(overloads.LogicalNot, argTypes(types.BoolType), types.BoolType), + decls.FunctionDocs(`logically negate a boolean value.`), + decls.Overload(overloads.LogicalNot, argTypes(types.BoolType), types.BoolType, + decls.OverloadExamples( + `!true // false`, + `!false // true`, + `!error // error`)), decls.SingletonUnaryBinding(func(val ref.Val) ref.Val { b, ok := val.(types.Bool) if !ok { @@ -90,66 +127,104 @@ func init() { // Equality / inequality. Special-cased in the interpreter function(operators.Equals, - decls.Overload(overloads.Equals, argTypes(paramA, paramA), types.BoolType), + decls.FunctionDocs(`compare two values of the same type for equality`), + decls.Overload(overloads.Equals, argTypes(paramA, paramA), types.BoolType, + decls.OverloadExamples( + `1 == 1 // true`, + `'hello' == 'world' // false`, + `bytes('hello') == b'hello' // true`, + `duration('1h') == duration('60m') // true`, + `dyn(3.0) == 3 // true`)), decls.SingletonBinaryBinding(noBinaryOverrides)), function(operators.NotEquals, - decls.Overload(overloads.NotEquals, argTypes(paramA, paramA), types.BoolType), + decls.FunctionDocs(`compare two values of the same type for inequality`), + decls.Overload(overloads.NotEquals, argTypes(paramA, paramA), types.BoolType, + decls.OverloadExamples( + `1 != 2 // true`, + `"a" != "a" // false`, + `3.0 != 3.1 // true`)), decls.SingletonBinaryBinding(noBinaryOverrides)), // Mathematical operators function(operators.Add, + decls.FunctionDocs( + `adds two numeric values or concatenates two strings, bytes,`, + `or lists.`), decls.Overload(overloads.AddBytes, - argTypes(types.BytesType, types.BytesType), types.BytesType), + argTypes(types.BytesType, types.BytesType), types.BytesType, + decls.OverloadExamples(`b'hi' + bytes('ya') // b'hiya'`)), decls.Overload(overloads.AddDouble, - argTypes(types.DoubleType, types.DoubleType), types.DoubleType), + argTypes(types.DoubleType, types.DoubleType), types.DoubleType, + decls.OverloadExamples(`3.14 + 1.59 // 4.73`)), decls.Overload(overloads.AddDurationDuration, - argTypes(types.DurationType, types.DurationType), types.DurationType), + argTypes(types.DurationType, types.DurationType), types.DurationType, + decls.OverloadExamples(`duration('1m') + duration('1s') // duration('1m1s')`)), decls.Overload(overloads.AddDurationTimestamp, - argTypes(types.DurationType, types.TimestampType), types.TimestampType), + argTypes(types.DurationType, types.TimestampType), types.TimestampType, + decls.OverloadExamples(`duration('24h') + timestamp('2023-01-01T00:00:00Z') // timestamp('2023-01-02T00:00:00Z')`)), decls.Overload(overloads.AddTimestampDuration, - argTypes(types.TimestampType, types.DurationType), types.TimestampType), + argTypes(types.TimestampType, types.DurationType), types.TimestampType, + decls.OverloadExamples(`timestamp('2023-01-01T00:00:00Z') + duration('24h1m2s') // timestamp('2023-01-02T00:01:02Z')`)), decls.Overload(overloads.AddInt64, - argTypes(types.IntType, types.IntType), types.IntType), + argTypes(types.IntType, types.IntType), types.IntType, + decls.OverloadExamples(`1 + 2 // 3`)), decls.Overload(overloads.AddList, - argTypes(listOfA, listOfA), listOfA), + argTypes(listOfA, listOfA), listOfA, + decls.OverloadExamples(`[1] + [2, 3] // [1, 2, 3]`)), decls.Overload(overloads.AddString, - argTypes(types.StringType, types.StringType), types.StringType), + argTypes(types.StringType, types.StringType), types.StringType, + decls.OverloadExamples(`"Hello, " + "world!" // "Hello, world!"`)), decls.Overload(overloads.AddUint64, - argTypes(types.UintType, types.UintType), types.UintType), + argTypes(types.UintType, types.UintType), types.UintType, + decls.OverloadExamples(`22u + 33u // 55u`)), decls.SingletonBinaryBinding(func(lhs, rhs ref.Val) ref.Val { return lhs.(traits.Adder).Add(rhs) }, traits.AdderType)), function(operators.Divide, + decls.FunctionDocs(`divide two numbers`), decls.Overload(overloads.DivideDouble, - argTypes(types.DoubleType, types.DoubleType), types.DoubleType), + argTypes(types.DoubleType, types.DoubleType), types.DoubleType, + decls.OverloadExamples(`7.0 / 2.0 // 3.5`)), decls.Overload(overloads.DivideInt64, - argTypes(types.IntType, types.IntType), types.IntType), + argTypes(types.IntType, types.IntType), types.IntType, + decls.OverloadExamples(`10 / 2 // 5`)), decls.Overload(overloads.DivideUint64, - argTypes(types.UintType, types.UintType), types.UintType), + argTypes(types.UintType, types.UintType), types.UintType, + decls.OverloadExamples(`42u / 2u // 21u`)), decls.SingletonBinaryBinding(func(lhs, rhs ref.Val) ref.Val { return lhs.(traits.Divider).Divide(rhs) }, traits.DividerType)), function(operators.Modulo, + decls.FunctionDocs(`compute the modulus of one integer into another`), decls.Overload(overloads.ModuloInt64, - argTypes(types.IntType, types.IntType), types.IntType), + argTypes(types.IntType, types.IntType), types.IntType, + decls.OverloadExamples(`3 % 2 // 1`)), decls.Overload(overloads.ModuloUint64, - argTypes(types.UintType, types.UintType), types.UintType), + argTypes(types.UintType, types.UintType), types.UintType, + decls.OverloadExamples(`6u % 3u // 0u`)), decls.SingletonBinaryBinding(func(lhs, rhs ref.Val) ref.Val { return lhs.(traits.Modder).Modulo(rhs) }, traits.ModderType)), function(operators.Multiply, + decls.FunctionDocs(`multiply two numbers`), decls.Overload(overloads.MultiplyDouble, - argTypes(types.DoubleType, types.DoubleType), types.DoubleType), + argTypes(types.DoubleType, types.DoubleType), types.DoubleType, + decls.OverloadExamples(`3.5 * 40.0 // 140.0`)), decls.Overload(overloads.MultiplyInt64, - argTypes(types.IntType, types.IntType), types.IntType), + argTypes(types.IntType, types.IntType), types.IntType, + decls.OverloadExamples(`-2 * 6 // -12`)), decls.Overload(overloads.MultiplyUint64, - argTypes(types.UintType, types.UintType), types.UintType), + argTypes(types.UintType, types.UintType), types.UintType, + decls.OverloadExamples(`13u * 3u // 39u`)), decls.SingletonBinaryBinding(func(lhs, rhs ref.Val) ref.Val { return lhs.(traits.Multiplier).Multiply(rhs) }, traits.MultiplierType)), function(operators.Negate, - decls.Overload(overloads.NegateDouble, argTypes(types.DoubleType), types.DoubleType), - decls.Overload(overloads.NegateInt64, argTypes(types.IntType), types.IntType), + decls.FunctionDocs(`negate a numeric value`), + decls.Overload(overloads.NegateDouble, argTypes(types.DoubleType), types.DoubleType, + decls.OverloadExamples(`-(3.14) // -3.14`)), + decls.Overload(overloads.NegateInt64, argTypes(types.IntType), types.IntType, + decls.OverloadExamples(`-(5) // -5`)), decls.SingletonUnaryBinding(func(val ref.Val) ref.Val { if types.IsBool(val) { return types.MaybeNoSuchOverloadErr(val) @@ -157,18 +232,32 @@ func init() { return val.(traits.Negater).Negate() }, traits.NegatorType)), function(operators.Subtract, + decls.FunctionDocs(`subtract two numbers, or two time-related values`), decls.Overload(overloads.SubtractDouble, - argTypes(types.DoubleType, types.DoubleType), types.DoubleType), + argTypes(types.DoubleType, types.DoubleType), types.DoubleType, + decls.OverloadExamples(`10.5 - 2.0 // 8.5`)), decls.Overload(overloads.SubtractDurationDuration, - argTypes(types.DurationType, types.DurationType), types.DurationType), + argTypes(types.DurationType, types.DurationType), types.DurationType, + decls.OverloadExamples(`duration('1m') - duration('1s') // duration('59s')`)), decls.Overload(overloads.SubtractInt64, - argTypes(types.IntType, types.IntType), types.IntType), + argTypes(types.IntType, types.IntType), types.IntType, + decls.OverloadExamples(`5 - 3 // 2`)), decls.Overload(overloads.SubtractTimestampDuration, - argTypes(types.TimestampType, types.DurationType), types.TimestampType), + argTypes(types.TimestampType, types.DurationType), types.TimestampType, + decls.OverloadExamples(common.MultilineDescription( + `timestamp('2023-01-10T12:00:00Z')`, + ` - duration('12h') // timestamp('2023-01-10T00:00:00Z')`))), decls.Overload(overloads.SubtractTimestampTimestamp, - argTypes(types.TimestampType, types.TimestampType), types.DurationType), + argTypes(types.TimestampType, types.TimestampType), types.DurationType, + decls.OverloadExamples(common.MultilineDescription( + `timestamp('2023-01-10T12:00:00Z')`, + ` - timestamp('2023-01-10T00:00:00Z') // duration('12h')`))), decls.Overload(overloads.SubtractUint64, - argTypes(types.UintType, types.UintType), types.UintType), + argTypes(types.UintType, types.UintType), types.UintType, + decls.OverloadExamples(common.MultilineDescription( + `// the subtraction result must be positive, otherwise an overflow`, + `// error is generated.`, + `42u - 3u // 39u`))), decls.SingletonBinaryBinding(func(lhs, rhs ref.Val) ref.Val { return lhs.(traits.Subtractor).Subtract(rhs) }, traits.SubtractorType)), @@ -176,35 +265,55 @@ func init() { // Relations operators function(operators.Less, + decls.FunctionDocs( + `compare two values and return true if the first value is`, + `less than the second`), decls.Overload(overloads.LessBool, - argTypes(types.BoolType, types.BoolType), types.BoolType), + argTypes(types.BoolType, types.BoolType), types.BoolType, + decls.OverloadExamples(`false < true // true`)), decls.Overload(overloads.LessInt64, - argTypes(types.IntType, types.IntType), types.BoolType), + argTypes(types.IntType, types.IntType), types.BoolType, + decls.OverloadExamples(`-2 < 3 // true`, `1 < 0 // false`)), decls.Overload(overloads.LessInt64Double, - argTypes(types.IntType, types.DoubleType), types.BoolType), + argTypes(types.IntType, types.DoubleType), types.BoolType, + decls.OverloadExamples(`1 < 1.1 // true`)), decls.Overload(overloads.LessInt64Uint64, - argTypes(types.IntType, types.UintType), types.BoolType), + argTypes(types.IntType, types.UintType), types.BoolType, + decls.OverloadExamples(`1 < 2u // true`)), decls.Overload(overloads.LessUint64, - argTypes(types.UintType, types.UintType), types.BoolType), + argTypes(types.UintType, types.UintType), types.BoolType, + decls.OverloadExamples(`1u < 2u // true`)), decls.Overload(overloads.LessUint64Double, - argTypes(types.UintType, types.DoubleType), types.BoolType), + argTypes(types.UintType, types.DoubleType), types.BoolType, + decls.OverloadExamples(`1u < 0.9 // false`)), decls.Overload(overloads.LessUint64Int64, - argTypes(types.UintType, types.IntType), types.BoolType), + argTypes(types.UintType, types.IntType), types.BoolType, + decls.OverloadExamples(`1u < 23 // true`, `1u < -1 // false`)), decls.Overload(overloads.LessDouble, - argTypes(types.DoubleType, types.DoubleType), types.BoolType), + argTypes(types.DoubleType, types.DoubleType), types.BoolType, + decls.OverloadExamples(`2.0 < 2.4 // true`)), decls.Overload(overloads.LessDoubleInt64, - argTypes(types.DoubleType, types.IntType), types.BoolType), + argTypes(types.DoubleType, types.IntType), types.BoolType, + decls.OverloadExamples(`2.1 < 3 // true`)), decls.Overload(overloads.LessDoubleUint64, - argTypes(types.DoubleType, types.UintType), types.BoolType), + argTypes(types.DoubleType, types.UintType), types.BoolType, + decls.OverloadExamples(`2.3 < 2u // false`, `-1.0 < 1u // true`)), decls.Overload(overloads.LessString, - argTypes(types.StringType, types.StringType), types.BoolType), + argTypes(types.StringType, types.StringType), types.BoolType, + decls.OverloadExamples(`'a' < 'b' // true`, `'cat' < 'cab' // false`)), decls.Overload(overloads.LessBytes, - argTypes(types.BytesType, types.BytesType), types.BoolType), + argTypes(types.BytesType, types.BytesType), types.BoolType, + decls.OverloadExamples(`b'hello' < b'world' // true`)), decls.Overload(overloads.LessTimestamp, - argTypes(types.TimestampType, types.TimestampType), types.BoolType), + argTypes(types.TimestampType, types.TimestampType), types.BoolType, + decls.OverloadExamples(`timestamp('2001-01-01T02:03:04Z') < timestamp('2002-02-02T02:03:04Z') // true`)), decls.Overload(overloads.LessDuration, - argTypes(types.DurationType, types.DurationType), types.BoolType), + 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 @@ -216,35 +325,55 @@ func init() { }, traits.ComparerType)), function(operators.LessEquals, + decls.FunctionDocs( + `compare two values and return true if the first value is`, + `less than or equal to the second`), decls.Overload(overloads.LessEqualsBool, - argTypes(types.BoolType, types.BoolType), types.BoolType), + argTypes(types.BoolType, types.BoolType), types.BoolType, + decls.OverloadExamples(`false <= true // true`)), decls.Overload(overloads.LessEqualsInt64, - argTypes(types.IntType, types.IntType), types.BoolType), + argTypes(types.IntType, types.IntType), types.BoolType, + decls.OverloadExamples(`-2 <= 3 // true`)), decls.Overload(overloads.LessEqualsInt64Double, - argTypes(types.IntType, types.DoubleType), types.BoolType), + argTypes(types.IntType, types.DoubleType), types.BoolType, + decls.OverloadExamples(`1 <= 1.1 // true`)), decls.Overload(overloads.LessEqualsInt64Uint64, - argTypes(types.IntType, types.UintType), types.BoolType), + argTypes(types.IntType, types.UintType), types.BoolType, + decls.OverloadExamples(`1 <= 2u // true`, `-1 <= 0u // true`)), decls.Overload(overloads.LessEqualsUint64, - argTypes(types.UintType, types.UintType), types.BoolType), + argTypes(types.UintType, types.UintType), types.BoolType, + decls.OverloadExamples(`1u <= 2u // true`)), decls.Overload(overloads.LessEqualsUint64Double, - argTypes(types.UintType, types.DoubleType), types.BoolType), + argTypes(types.UintType, types.DoubleType), types.BoolType, + decls.OverloadExamples(`1u <= 1.0 // true`, `1u <= 1.1 // true`)), decls.Overload(overloads.LessEqualsUint64Int64, - argTypes(types.UintType, types.IntType), types.BoolType), + argTypes(types.UintType, types.IntType), types.BoolType, + decls.OverloadExamples(`1u <= 23 // true`)), decls.Overload(overloads.LessEqualsDouble, - argTypes(types.DoubleType, types.DoubleType), types.BoolType), + argTypes(types.DoubleType, types.DoubleType), types.BoolType, + decls.OverloadExamples(`2.0 <= 2.4 // true`)), decls.Overload(overloads.LessEqualsDoubleInt64, - argTypes(types.DoubleType, types.IntType), types.BoolType), + argTypes(types.DoubleType, types.IntType), types.BoolType, + decls.OverloadExamples(`2.1 <= 3 // true`)), decls.Overload(overloads.LessEqualsDoubleUint64, - argTypes(types.DoubleType, types.UintType), types.BoolType), + argTypes(types.DoubleType, types.UintType), types.BoolType, + decls.OverloadExamples(`2.0 <= 2u // true`, `-1.0 <= 1u // true`)), decls.Overload(overloads.LessEqualsString, - argTypes(types.StringType, types.StringType), types.BoolType), + argTypes(types.StringType, types.StringType), types.BoolType, + decls.OverloadExamples(`'a' <= 'b' // true`, `'a' <= 'a' // true`, `'cat' <= 'cab' // false`)), decls.Overload(overloads.LessEqualsBytes, - argTypes(types.BytesType, types.BytesType), types.BoolType), + argTypes(types.BytesType, types.BytesType), types.BoolType, + decls.OverloadExamples(`b'hello' <= b'world' // true`)), decls.Overload(overloads.LessEqualsTimestamp, - argTypes(types.TimestampType, types.TimestampType), types.BoolType), + argTypes(types.TimestampType, types.TimestampType), types.BoolType, + decls.OverloadExamples(`timestamp('2001-01-01T02:03:04Z') <= timestamp('2002-02-02T02:03:04Z') // true`)), decls.Overload(overloads.LessEqualsDuration, - argTypes(types.DurationType, types.DurationType), types.BoolType), + 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 @@ -256,35 +385,55 @@ func init() { }, traits.ComparerType)), function(operators.Greater, + decls.FunctionDocs( + `compare two values and return true if the first value is`, + `greater than the second`), decls.Overload(overloads.GreaterBool, - argTypes(types.BoolType, types.BoolType), types.BoolType), + argTypes(types.BoolType, types.BoolType), types.BoolType, + decls.OverloadExamples(`true > false // true`)), decls.Overload(overloads.GreaterInt64, - argTypes(types.IntType, types.IntType), types.BoolType), + argTypes(types.IntType, types.IntType), types.BoolType, + decls.OverloadExamples(`3 > -2 // true`)), decls.Overload(overloads.GreaterInt64Double, - argTypes(types.IntType, types.DoubleType), types.BoolType), + argTypes(types.IntType, types.DoubleType), types.BoolType, + decls.OverloadExamples(`2 > 1.1 // true`)), decls.Overload(overloads.GreaterInt64Uint64, - argTypes(types.IntType, types.UintType), types.BoolType), + argTypes(types.IntType, types.UintType), types.BoolType, + decls.OverloadExamples(`3 > 2u // true`)), decls.Overload(overloads.GreaterUint64, - argTypes(types.UintType, types.UintType), types.BoolType), + argTypes(types.UintType, types.UintType), types.BoolType, + decls.OverloadExamples(`2u > 1u // true`)), decls.Overload(overloads.GreaterUint64Double, - argTypes(types.UintType, types.DoubleType), types.BoolType), + argTypes(types.UintType, types.DoubleType), types.BoolType, + decls.OverloadExamples(`2u > 1.9 // true`)), decls.Overload(overloads.GreaterUint64Int64, - argTypes(types.UintType, types.IntType), types.BoolType), + argTypes(types.UintType, types.IntType), types.BoolType, + decls.OverloadExamples(`23u > 1 // true`, `0u > -1 // true`)), decls.Overload(overloads.GreaterDouble, - argTypes(types.DoubleType, types.DoubleType), types.BoolType), + argTypes(types.DoubleType, types.DoubleType), types.BoolType, + decls.OverloadExamples(`2.4 > 2.0 // true`)), decls.Overload(overloads.GreaterDoubleInt64, - argTypes(types.DoubleType, types.IntType), types.BoolType), + argTypes(types.DoubleType, types.IntType), types.BoolType, + decls.OverloadExamples(`3.1 > 3 // true`, `3.0 > 3 // false`)), decls.Overload(overloads.GreaterDoubleUint64, - argTypes(types.DoubleType, types.UintType), types.BoolType), + argTypes(types.DoubleType, types.UintType), types.BoolType, + decls.OverloadExamples(`2.3 > 2u // true`)), decls.Overload(overloads.GreaterString, - argTypes(types.StringType, types.StringType), types.BoolType), + argTypes(types.StringType, types.StringType), types.BoolType, + decls.OverloadExamples(`'b' > 'a' // true`)), decls.Overload(overloads.GreaterBytes, - argTypes(types.BytesType, types.BytesType), types.BoolType), + argTypes(types.BytesType, types.BytesType), types.BoolType, + decls.OverloadExamples(`b'world' > b'hello' // true`)), decls.Overload(overloads.GreaterTimestamp, - argTypes(types.TimestampType, types.TimestampType), types.BoolType), + argTypes(types.TimestampType, types.TimestampType), types.BoolType, + decls.OverloadExamples(`timestamp('2002-02-02T02:03:04Z') > timestamp('2001-01-01T02:03:04Z') // true`)), decls.Overload(overloads.GreaterDuration, - argTypes(types.DurationType, types.DurationType), types.BoolType), + 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 @@ -296,35 +445,55 @@ func init() { }, traits.ComparerType)), function(operators.GreaterEquals, + decls.FunctionDocs( + `compare two values and return true if the first value is`, + `greater than or equal to the second`), decls.Overload(overloads.GreaterEqualsBool, - argTypes(types.BoolType, types.BoolType), types.BoolType), + argTypes(types.BoolType, types.BoolType), types.BoolType, + decls.OverloadExamples(`true >= false // true`)), decls.Overload(overloads.GreaterEqualsInt64, - argTypes(types.IntType, types.IntType), types.BoolType), + argTypes(types.IntType, types.IntType), types.BoolType, + decls.OverloadExamples(`3 >= -2 // true`)), decls.Overload(overloads.GreaterEqualsInt64Double, - argTypes(types.IntType, types.DoubleType), types.BoolType), + argTypes(types.IntType, types.DoubleType), types.BoolType, + decls.OverloadExamples(`2 >= 1.1 // true`, `1 >= 1.0 // true`)), decls.Overload(overloads.GreaterEqualsInt64Uint64, - argTypes(types.IntType, types.UintType), types.BoolType), + argTypes(types.IntType, types.UintType), types.BoolType, + decls.OverloadExamples(`3 >= 2u // true`)), decls.Overload(overloads.GreaterEqualsUint64, - argTypes(types.UintType, types.UintType), types.BoolType), + argTypes(types.UintType, types.UintType), types.BoolType, + decls.OverloadExamples(`2u >= 1u // true`)), decls.Overload(overloads.GreaterEqualsUint64Double, - argTypes(types.UintType, types.DoubleType), types.BoolType), + argTypes(types.UintType, types.DoubleType), types.BoolType, + decls.OverloadExamples(`2u >= 1.9 // true`)), decls.Overload(overloads.GreaterEqualsUint64Int64, - argTypes(types.UintType, types.IntType), types.BoolType), + argTypes(types.UintType, types.IntType), types.BoolType, + decls.OverloadExamples(`23u >= 1 // true`, `1u >= 1 // true`)), decls.Overload(overloads.GreaterEqualsDouble, - argTypes(types.DoubleType, types.DoubleType), types.BoolType), + argTypes(types.DoubleType, types.DoubleType), types.BoolType, + decls.OverloadExamples(`2.4 >= 2.0 // true`)), decls.Overload(overloads.GreaterEqualsDoubleInt64, - argTypes(types.DoubleType, types.IntType), types.BoolType), + argTypes(types.DoubleType, types.IntType), types.BoolType, + decls.OverloadExamples(`3.1 >= 3 // true`)), decls.Overload(overloads.GreaterEqualsDoubleUint64, - argTypes(types.DoubleType, types.UintType), types.BoolType), + argTypes(types.DoubleType, types.UintType), types.BoolType, + decls.OverloadExamples(`2.3 >= 2u // true`)), decls.Overload(overloads.GreaterEqualsString, - argTypes(types.StringType, types.StringType), types.BoolType), + argTypes(types.StringType, types.StringType), types.BoolType, + decls.OverloadExamples(`'b' >= 'a' // true`)), decls.Overload(overloads.GreaterEqualsBytes, - argTypes(types.BytesType, types.BytesType), types.BoolType), + argTypes(types.BytesType, types.BytesType), types.BoolType, + decls.OverloadExamples(`b'world' >= b'hello' // true`)), decls.Overload(overloads.GreaterEqualsTimestamp, - argTypes(types.TimestampType, types.TimestampType), types.BoolType), + argTypes(types.TimestampType, types.TimestampType), types.BoolType, + decls.OverloadExamples(`timestamp('2001-01-01T02:03:04Z') >= timestamp('2001-01-01T02:03:04Z') // true`)), decls.Overload(overloads.GreaterEqualsDuration, - argTypes(types.DurationType, types.DurationType), types.BoolType), + 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 @@ -337,16 +506,28 @@ func init() { // Indexing function(operators.Index, - decls.Overload(overloads.IndexList, argTypes(listOfA, types.IntType), paramA), - decls.Overload(overloads.IndexMap, argTypes(mapOfAB, paramA), paramB), + decls.FunctionDocs(`select a value from a list by index, or value from a map by key`), + decls.Overload(overloads.IndexList, argTypes(listOfA, types.IntType), paramA, + decls.OverloadExamples(`[1, 2, 3][1] // 2`)), + decls.Overload(overloads.IndexMap, argTypes(mapOfAB, paramA), paramB, + decls.OverloadExamples( + `{'key': 'value'}['key'] // 'value'`, + `{'key': 'value'}['missing'] // error`)), decls.SingletonBinaryBinding(func(lhs, rhs ref.Val) ref.Val { return lhs.(traits.Indexer).Get(rhs) }, traits.IndexerType)), // Collections operators function(operators.In, - decls.Overload(overloads.InList, argTypes(paramA, listOfA), types.BoolType), - decls.Overload(overloads.InMap, argTypes(paramA, mapOfAB), types.BoolType), + decls.FunctionDocs(`test whether a value exists in a list, or a key exists in a map`), + decls.Overload(overloads.InList, argTypes(paramA, listOfA), types.BoolType, + decls.OverloadExamples( + `2 in [1, 2, 3] // true`, + `"a" in ["b", "c"] // false`)), + decls.Overload(overloads.InMap, argTypes(paramA, mapOfAB), types.BoolType, + decls.OverloadExamples( + `'key1' in {'key1': 'value1', 'key2': 'value2'} // true`, + `3 in {1: "one", 2: "two"} // false`)), decls.SingletonBinaryBinding(inAggregate)), function(operators.OldIn, decls.DisableDeclaration(true), // safe deprecation @@ -359,209 +540,362 @@ func init() { decls.Overload(overloads.InMap, argTypes(paramA, mapOfAB), types.BoolType), decls.SingletonBinaryBinding(inAggregate)), function(overloads.Size, - decls.Overload(overloads.SizeBytes, argTypes(types.BytesType), types.IntType), - decls.MemberOverload(overloads.SizeBytesInst, argTypes(types.BytesType), types.IntType), - decls.Overload(overloads.SizeList, argTypes(listOfA), types.IntType), - decls.MemberOverload(overloads.SizeListInst, argTypes(listOfA), types.IntType), - decls.Overload(overloads.SizeMap, argTypes(mapOfAB), types.IntType), - decls.MemberOverload(overloads.SizeMapInst, argTypes(mapOfAB), types.IntType), - decls.Overload(overloads.SizeString, argTypes(types.StringType), types.IntType), - decls.MemberOverload(overloads.SizeStringInst, argTypes(types.StringType), types.IntType), + decls.FunctionDocs( + `compute the size of a list or map, the number of characters in a string,`, + `or the number of bytes in a sequence`), + decls.Overload(overloads.SizeBytes, argTypes(types.BytesType), types.IntType, + decls.OverloadExamples(`size(b'123') // 3`)), + decls.MemberOverload(overloads.SizeBytesInst, argTypes(types.BytesType), types.IntType, + decls.OverloadExamples(`b'123'.size() // 3`)), + decls.Overload(overloads.SizeList, argTypes(listOfA), types.IntType, + decls.OverloadExamples(`size([1, 2, 3]) // 3`)), + decls.MemberOverload(overloads.SizeListInst, argTypes(listOfA), types.IntType, + decls.OverloadExamples(`[1, 2, 3].size() // 3`)), + decls.Overload(overloads.SizeMap, argTypes(mapOfAB), types.IntType, + decls.OverloadExamples(`size({'a': 1, 'b': 2}) // 2`)), + decls.MemberOverload(overloads.SizeMapInst, argTypes(mapOfAB), types.IntType, + decls.OverloadExamples(`{'a': 1, 'b': 2}.size() // 2`)), + decls.Overload(overloads.SizeString, argTypes(types.StringType), types.IntType, + decls.OverloadExamples(`size('hello') // 5`)), + decls.MemberOverload(overloads.SizeStringInst, argTypes(types.StringType), types.IntType, + decls.OverloadExamples(`'hello'.size() // 5`)), decls.SingletonUnaryBinding(func(val ref.Val) ref.Val { return val.(traits.Sizer).Size() }, traits.SizerType)), // Type conversions function(overloads.TypeConvertType, - decls.Overload(overloads.TypeConvertType, argTypes(paramA), types.NewTypeTypeWithParam(paramA)), + decls.FunctionDocs(`convert a value to its type identifier`), + decls.Overload(overloads.TypeConvertType, argTypes(paramA), types.NewTypeTypeWithParam(paramA), + decls.OverloadExamples( + `type(1) // int`, + `type('hello') // string`, + `type(int) // type`, + `type(type) // type`)), decls.SingletonUnaryBinding(convertToType(types.TypeType))), // Bool conversions function(overloads.TypeConvertBool, + decls.FunctionDocs(`convert a value to a boolean`), decls.Overload(overloads.BoolToBool, argTypes(types.BoolType), types.BoolType, + + decls.OverloadExamples(`bool(true) // true`), decls.UnaryBinding(identity)), decls.Overload(overloads.StringToBool, argTypes(types.StringType), types.BoolType, + + decls.OverloadExamples(`bool('true') // true`, `bool('false') // false`), decls.UnaryBinding(convertToType(types.BoolType)))), // Bytes conversions function(overloads.TypeConvertBytes, + decls.FunctionDocs(`convert a value to bytes`), decls.Overload(overloads.BytesToBytes, argTypes(types.BytesType), types.BytesType, + decls.OverloadExamples(`bytes(b'abc') // b'abc'`), decls.UnaryBinding(identity)), decls.Overload(overloads.StringToBytes, argTypes(types.StringType), types.BytesType, + decls.OverloadExamples(`bytes('hello') // b'hello'`), decls.UnaryBinding(convertToType(types.BytesType)))), // Double conversions function(overloads.TypeConvertDouble, + decls.FunctionDocs(`convert a value to a double`), decls.Overload(overloads.DoubleToDouble, argTypes(types.DoubleType), types.DoubleType, + decls.OverloadExamples(`double(1.23) // 1.23`), decls.UnaryBinding(identity)), decls.Overload(overloads.IntToDouble, argTypes(types.IntType), types.DoubleType, + decls.OverloadExamples(`double(123) // 123.0`), decls.UnaryBinding(convertToType(types.DoubleType))), decls.Overload(overloads.StringToDouble, argTypes(types.StringType), types.DoubleType, + decls.OverloadExamples(`double('1.23') // 1.23`), decls.UnaryBinding(convertToType(types.DoubleType))), decls.Overload(overloads.UintToDouble, argTypes(types.UintType), types.DoubleType, + decls.OverloadExamples(`double(123u) // 123.0`), decls.UnaryBinding(convertToType(types.DoubleType)))), // Duration conversions function(overloads.TypeConvertDuration, + decls.FunctionDocs(`convert a value to a google.protobuf.Duration`), 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)))), // Dyn conversions function(overloads.TypeConvertDyn, - decls.Overload(overloads.ToDyn, argTypes(paramA), types.DynType), + decls.FunctionDocs(`indicate that the type is dynamic for type-checking purposes`), + decls.Overload(overloads.ToDyn, argTypes(paramA), types.DynType, + decls.OverloadExamples(`dyn(1) // 1`)), decls.SingletonUnaryBinding(identity)), // Int conversions function(overloads.TypeConvertInt, + decls.FunctionDocs(`convert a value to an int`), decls.Overload(overloads.IntToInt, argTypes(types.IntType), types.IntType, + decls.OverloadExamples(`int(123) // 123`), decls.UnaryBinding(identity)), decls.Overload(overloads.DoubleToInt, argTypes(types.DoubleType), types.IntType, + decls.OverloadExamples(`int(123.45) // 123`), decls.UnaryBinding(convertToType(types.IntType))), decls.Overload(overloads.DurationToInt, argTypes(types.DurationType), types.IntType, - decls.UnaryBinding(convertToType(types.IntType))), + decls.OverloadExamples(`int(duration('1s')) // 1000000000`), + decls.UnaryBinding(convertToType(types.IntType))), // Duration to nanoseconds decls.Overload(overloads.StringToInt, argTypes(types.StringType), types.IntType, + decls.OverloadExamples(`int('123') // 123`, `int('-456') // -456`), decls.UnaryBinding(convertToType(types.IntType))), decls.Overload(overloads.TimestampToInt, argTypes(types.TimestampType), types.IntType, - decls.UnaryBinding(convertToType(types.IntType))), + decls.OverloadExamples(`int(timestamp('1970-01-01T00:00:01Z')) // 1`), + decls.UnaryBinding(convertToType(types.IntType))), // Timestamp to epoch seconds decls.Overload(overloads.UintToInt, argTypes(types.UintType), types.IntType, - decls.UnaryBinding(convertToType(types.IntType))), - ), + decls.OverloadExamples(`int(123u) // 123`), + decls.UnaryBinding(convertToType(types.IntType)))), // String conversions function(overloads.TypeConvertString, + decls.FunctionDocs(`convert a value to a string`), decls.Overload(overloads.StringToString, argTypes(types.StringType), types.StringType, + decls.OverloadExamples(`string('hello') // 'hello'`), decls.UnaryBinding(identity)), decls.Overload(overloads.BoolToString, argTypes(types.BoolType), types.StringType, + decls.OverloadExamples(`string(true) // 'true'`), decls.UnaryBinding(convertToType(types.StringType))), decls.Overload(overloads.BytesToString, argTypes(types.BytesType), types.StringType, + decls.OverloadExamples(`string(b'hello') // 'hello'`), decls.UnaryBinding(convertToType(types.StringType))), decls.Overload(overloads.DoubleToString, argTypes(types.DoubleType), types.StringType, - decls.UnaryBinding(convertToType(types.StringType))), + decls.UnaryBinding(convertToType(types.StringType)), + decls.OverloadExamples(`string(-1.23e4) // '-12300'`)), decls.Overload(overloads.DurationToString, argTypes(types.DurationType), types.StringType, + decls.OverloadExamples(`string(duration('1h30m')) // '5400s'`), decls.UnaryBinding(convertToType(types.StringType))), decls.Overload(overloads.IntToString, argTypes(types.IntType), types.StringType, + decls.OverloadExamples(`string(-123) // '-123'`), decls.UnaryBinding(convertToType(types.StringType))), decls.Overload(overloads.TimestampToString, argTypes(types.TimestampType), types.StringType, + decls.OverloadExamples(`string(timestamp('1970-01-01T00:00:00Z')) // '1970-01-01T00:00:00Z'`), decls.UnaryBinding(convertToType(types.StringType))), decls.Overload(overloads.UintToString, argTypes(types.UintType), types.StringType, + decls.OverloadExamples(`string(123u) // '123'`), decls.UnaryBinding(convertToType(types.StringType)))), // Timestamp conversions function(overloads.TypeConvertTimestamp, + decls.FunctionDocs(`convert a value to a google.protobuf.Timestamp`), decls.Overload(overloads.TimestampToTimestamp, argTypes(types.TimestampType), types.TimestampType, + decls.OverloadExamples(`timestamp(timestamp('2023-01-01T00:00:00Z')) // timestamp('2023-01-01T00:00:00Z')`), decls.UnaryBinding(identity)), decls.Overload(overloads.IntToTimestamp, argTypes(types.IntType), types.TimestampType, + decls.OverloadExamples(`timestamp(1) // timestamp('1970-01-01T00:00:01Z')`), // Epoch seconds to Timestamp decls.UnaryBinding(convertToType(types.TimestampType))), decls.Overload(overloads.StringToTimestamp, argTypes(types.StringType), types.TimestampType, + decls.OverloadExamples(`timestamp('2025-01-01T12:34:56Z') // timestamp('2025-01-01T12:34:56Z')`), decls.UnaryBinding(convertToType(types.TimestampType)))), // Uint conversions function(overloads.TypeConvertUint, + decls.FunctionDocs(`convert a value to a uint`), decls.Overload(overloads.UintToUint, argTypes(types.UintType), types.UintType, + decls.OverloadExamples(`uint(123u) // 123u`), decls.UnaryBinding(identity)), decls.Overload(overloads.DoubleToUint, argTypes(types.DoubleType), types.UintType, + decls.OverloadExamples(`uint(123.45) // 123u`), decls.UnaryBinding(convertToType(types.UintType))), decls.Overload(overloads.IntToUint, argTypes(types.IntType), types.UintType, + decls.OverloadExamples(`uint(123) // 123u`), decls.UnaryBinding(convertToType(types.UintType))), decls.Overload(overloads.StringToUint, argTypes(types.StringType), types.UintType, + decls.OverloadExamples(`uint('123') // 123u`), decls.UnaryBinding(convertToType(types.UintType)))), // String functions function(overloads.Contains, + decls.FunctionDocs(`test whether a string contains a substring`), decls.MemberOverload(overloads.ContainsString, argTypes(types.StringType, types.StringType), types.BoolType, + decls.OverloadExamples( + `'hello world'.contains('o w') // true`, + `'hello world'.contains('goodbye') // false`), decls.BinaryBinding(types.StringContains)), decls.DisableTypeGuards(true)), function(overloads.EndsWith, + decls.FunctionDocs(`test whether a string ends with a substring suffix`), decls.MemberOverload(overloads.EndsWithString, argTypes(types.StringType, types.StringType), types.BoolType, + decls.OverloadExamples( + `'hello world'.endsWith('world') // true`, + `'hello world'.endsWith('hello') // false`), decls.BinaryBinding(types.StringEndsWith)), decls.DisableTypeGuards(true)), function(overloads.StartsWith, + decls.FunctionDocs(`test whether a string starts with a substring prefix`), decls.MemberOverload(overloads.StartsWithString, argTypes(types.StringType, types.StringType), types.BoolType, + decls.OverloadExamples( + `'hello world'.startsWith('hello') // true`, + `'hello world'.startsWith('world') // false`), decls.BinaryBinding(types.StringStartsWith)), decls.DisableTypeGuards(true)), function(overloads.Matches, - decls.Overload(overloads.Matches, argTypes(types.StringType, types.StringType), types.BoolType), + decls.FunctionDocs(`test whether a string matches an RE2 regular expression`), + decls.Overload(overloads.Matches, argTypes(types.StringType, types.StringType), types.BoolType, + decls.OverloadExamples( + `matches('123-456', '^[0-9]+(-[0-9]+)?$') // true`, + `matches('hello', '^h.*o$') // true`)), decls.MemberOverload(overloads.MatchesString, - argTypes(types.StringType, types.StringType), types.BoolType), + argTypes(types.StringType, types.StringType), types.BoolType, + decls.OverloadExamples( + `'123-456'.matches('^[0-9]+(-[0-9]+)?$') // true`, + `'hello'.matches('^h.*o$') // true`)), decls.SingletonBinaryBinding(func(str, pat ref.Val) ref.Val { return str.(traits.Matcher).Match(pat) }, traits.MatcherType)), // Timestamp / duration functions function(overloads.TimeGetFullYear, + decls.FunctionDocs(`get the 0-based full year from a timestamp, UTC unless an IANA timezone is specified.`), decls.MemberOverload(overloads.TimestampToYear, - argTypes(types.TimestampType), types.IntType), + argTypes(types.TimestampType), types.IntType, + decls.OverloadExamples(`timestamp('2023-07-14T10:30:45.123Z').getFullYear() // 2023`), + decls.UnaryBinding(func(ts ref.Val) ref.Val { + return timestampGetFullYear(ts, utcTZ) + })), decls.MemberOverload(overloads.TimestampToYearWithTz, - argTypes(types.TimestampType, types.StringType), types.IntType)), + argTypes(types.TimestampType, types.StringType), types.IntType, + decls.OverloadExamples(`timestamp('2023-01-01T05:30:00Z').getFullYear('-08:00') // 2022`), + decls.BinaryBinding(timestampGetFullYear))), function(overloads.TimeGetMonth, + decls.FunctionDocs(`get the 0-based month from a timestamp, UTC unless an IANA timezone is specified.`), decls.MemberOverload(overloads.TimestampToMonth, - argTypes(types.TimestampType), types.IntType), + argTypes(types.TimestampType), types.IntType, + decls.OverloadExamples(`timestamp('2023-07-14T10:30:45.123Z').getMonth() // 6`), // July is month 6 + decls.UnaryBinding(func(ts ref.Val) ref.Val { + return timestampGetMonth(ts, utcTZ) + })), decls.MemberOverload(overloads.TimestampToMonthWithTz, - argTypes(types.TimestampType, types.StringType), types.IntType)), + argTypes(types.TimestampType, types.StringType), types.IntType, + decls.OverloadExamples(`timestamp('2023-01-01T05:30:00Z').getMonth('America/Los_Angeles') // 11`), // December is month 11 + decls.BinaryBinding(timestampGetMonth))), function(overloads.TimeGetDayOfYear, + decls.FunctionDocs(`get the 0-based day of the year from a timestamp, UTC unless an IANA timezone is specified.`), decls.MemberOverload(overloads.TimestampToDayOfYear, - argTypes(types.TimestampType), types.IntType), + argTypes(types.TimestampType), types.IntType, + decls.OverloadExamples(`timestamp('2023-01-02T00:00:00Z').getDayOfYear() // 1`), + decls.UnaryBinding(func(ts ref.Val) ref.Val { + return timestampGetDayOfYear(ts, utcTZ) + })), decls.MemberOverload(overloads.TimestampToDayOfYearWithTz, - argTypes(types.TimestampType, types.StringType), types.IntType)), + argTypes(types.TimestampType, types.StringType), types.IntType, + decls.OverloadExamples(`timestamp('2023-01-01T05:00:00Z').getDayOfYear('America/Los_Angeles') // 364`), + decls.BinaryBinding(timestampGetDayOfYear))), function(overloads.TimeGetDayOfMonth, + decls.FunctionDocs(`get the 0-based day of the month from a timestamp, UTC unless an IANA timezone is specified.`), decls.MemberOverload(overloads.TimestampToDayOfMonthZeroBased, - argTypes(types.TimestampType), types.IntType), + argTypes(types.TimestampType), types.IntType, + decls.OverloadExamples(`timestamp('2023-07-14T10:30:45.123Z').getDayOfMonth() // 13`), + decls.UnaryBinding(func(ts ref.Val) ref.Val { + return timestampGetDayOfMonthZeroBased(ts, utcTZ) + })), decls.MemberOverload(overloads.TimestampToDayOfMonthZeroBasedWithTz, - argTypes(types.TimestampType, types.StringType), types.IntType)), + argTypes(types.TimestampType, types.StringType), types.IntType, + decls.OverloadExamples(`timestamp('2023-07-01T05:00:00Z').getDayOfMonth('America/Los_Angeles') // 29`), + decls.BinaryBinding(timestampGetDayOfMonthZeroBased))), function(overloads.TimeGetDate, + decls.FunctionDocs(`get the 1-based day of the month from a timestamp, UTC unless an IANA timezone is specified.`), decls.MemberOverload(overloads.TimestampToDayOfMonthOneBased, - argTypes(types.TimestampType), types.IntType), + argTypes(types.TimestampType), types.IntType, + decls.OverloadExamples(`timestamp('2023-07-14T10:30:45.123Z').getDate() // 14`), + decls.UnaryBinding(func(ts ref.Val) ref.Val { + return timestampGetDayOfMonthOneBased(ts, utcTZ) + })), decls.MemberOverload(overloads.TimestampToDayOfMonthOneBasedWithTz, - argTypes(types.TimestampType, types.StringType), types.IntType)), + argTypes(types.TimestampType, types.StringType), types.IntType, + decls.OverloadExamples(`timestamp('2023-07-01T05:00:00Z').getDate('America/Los_Angeles') // 30`), + decls.BinaryBinding(timestampGetDayOfMonthOneBased))), function(overloads.TimeGetDayOfWeek, + decls.FunctionDocs(`get the 0-based day of the week from a timestamp, UTC unless an IANA timezone is specified.`), decls.MemberOverload(overloads.TimestampToDayOfWeek, - argTypes(types.TimestampType), types.IntType), + argTypes(types.TimestampType), types.IntType, + decls.OverloadExamples(`timestamp('2023-07-14T10:30:45.123Z').getDayOfWeek() // 5`), // Friday is day 5 + decls.UnaryBinding(func(ts ref.Val) ref.Val { + return timestampGetDayOfWeek(ts, utcTZ) + })), decls.MemberOverload(overloads.TimestampToDayOfWeekWithTz, - argTypes(types.TimestampType, types.StringType), types.IntType)), + argTypes(types.TimestampType, types.StringType), types.IntType, + decls.OverloadExamples(`timestamp('2023-07-16T05:00:00Z').getDayOfWeek('America/Los_Angeles') // 6`), // Saturday is day 6 + decls.BinaryBinding(timestampGetDayOfWeek))), function(overloads.TimeGetHours, + decls.FunctionDocs(`get the hours portion from a timestamp, or convert a duration to hours`), decls.MemberOverload(overloads.TimestampToHours, - argTypes(types.TimestampType), types.IntType), + argTypes(types.TimestampType), types.IntType, + decls.OverloadExamples(`timestamp('2023-07-14T10:30:45.123Z').getHours() // 10`), + decls.UnaryBinding(func(ts ref.Val) ref.Val { + return timestampGetHours(ts, utcTZ) + })), decls.MemberOverload(overloads.TimestampToHoursWithTz, - argTypes(types.TimestampType, types.StringType), types.IntType), + argTypes(types.TimestampType, types.StringType), types.IntType, + decls.OverloadExamples(`timestamp('2023-07-14T10:30:45.123Z').getHours('America/Los_Angeles') // 2`), + decls.BinaryBinding(timestampGetHours)), decls.MemberOverload(overloads.DurationToHours, - argTypes(types.DurationType), types.IntType)), + argTypes(types.DurationType), types.IntType, + decls.OverloadExamples(`duration('3723s').getHours() // 1`), + decls.UnaryBinding(types.DurationGetHours))), function(overloads.TimeGetMinutes, + decls.FunctionDocs(`get the minutes portion from a timestamp, or convert a duration to minutes`), decls.MemberOverload(overloads.TimestampToMinutes, - argTypes(types.TimestampType), types.IntType), + argTypes(types.TimestampType), types.IntType, + decls.OverloadExamples(`timestamp('2023-07-14T10:30:45.123Z').getMinutes() // 30`), + decls.UnaryBinding(func(ts ref.Val) ref.Val { + return timestampGetMinutes(ts, utcTZ) + })), decls.MemberOverload(overloads.TimestampToMinutesWithTz, - argTypes(types.TimestampType, types.StringType), types.IntType), + argTypes(types.TimestampType, types.StringType), types.IntType, + decls.OverloadExamples(`timestamp('2023-07-14T10:30:45.123Z').getMinutes('America/Los_Angeles') // 30`), + decls.BinaryBinding(timestampGetMinutes)), decls.MemberOverload(overloads.DurationToMinutes, - argTypes(types.DurationType), types.IntType)), + argTypes(types.DurationType), types.IntType, + decls.OverloadExamples(`duration('3723s').getMinutes() // 62`), + decls.UnaryBinding(types.DurationGetMinutes))), function(overloads.TimeGetSeconds, + decls.FunctionDocs(`get the seconds portion from a timestamp, or convert a duration to seconds`), decls.MemberOverload(overloads.TimestampToSeconds, - argTypes(types.TimestampType), types.IntType), + argTypes(types.TimestampType), types.IntType, + decls.OverloadExamples(`timestamp('2023-07-14T10:30:45.123Z').getSeconds() // 45`), + decls.UnaryBinding(func(ts ref.Val) ref.Val { + return timestampGetSeconds(ts, utcTZ) + })), decls.MemberOverload(overloads.TimestampToSecondsWithTz, - argTypes(types.TimestampType, types.StringType), types.IntType), + argTypes(types.TimestampType, types.StringType), types.IntType, + decls.OverloadExamples(`timestamp('2023-07-14T10:30:45.123Z').getSeconds('America/Los_Angeles') // 45`), + decls.BinaryBinding(timestampGetSeconds)), decls.MemberOverload(overloads.DurationToSeconds, - argTypes(types.DurationType), types.IntType)), + argTypes(types.DurationType), types.IntType, + decls.OverloadExamples(`duration('3723.456s').getSeconds() // 3723`), + decls.UnaryBinding(types.DurationGetSeconds))), function(overloads.TimeGetMilliseconds, + decls.FunctionDocs(`get the milliseconds portion from a timestamp`), decls.MemberOverload(overloads.TimestampToMilliseconds, - argTypes(types.TimestampType), types.IntType), + argTypes(types.TimestampType), types.IntType, + decls.OverloadExamples(`timestamp('2023-07-14T10:30:45.123Z').getMilliseconds() // 123`), + decls.UnaryBinding(func(ts ref.Val) ref.Val { + return timestampGetMilliseconds(ts, utcTZ) + })), decls.MemberOverload(overloads.TimestampToMillisecondsWithTz, - argTypes(types.TimestampType, types.StringType), types.IntType), + argTypes(types.TimestampType, types.StringType), types.IntType, + decls.OverloadExamples(`timestamp('2023-07-14T10:30:45.123Z').getMilliseconds('America/Los_Angeles') // 123`), + decls.BinaryBinding(timestampGetMilliseconds)), decls.MemberOverload(overloads.DurationToMilliseconds, - argTypes(types.DurationType), types.IntType)), + argTypes(types.DurationType), types.IntType, + decls.UnaryBinding(types.DurationGetMilliseconds))), } } @@ -605,6 +939,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() } @@ -618,3 +957,118 @@ func convertToType(t ref.Type) functions.UnaryOp { return val.ConvertToType(t) } } + +func timestampGetFullYear(ts, tz ref.Val) ref.Val { + t, err := inTimeZone(ts, tz) + if err != nil { + return types.NewErrFromString(err.Error()) + } + return types.Int(t.Year()) +} + +func timestampGetMonth(ts, tz ref.Val) ref.Val { + t, err := inTimeZone(ts, tz) + if err != nil { + return types.NewErrFromString(err.Error()) + } + // CEL spec indicates that the month should be 0-based, but the Time value + // for Month() is 1-based. + return types.Int(t.Month() - 1) +} + +func timestampGetDayOfYear(ts, tz ref.Val) ref.Val { + t, err := inTimeZone(ts, tz) + if err != nil { + return types.NewErrFromString(err.Error()) + } + return types.Int(t.YearDay() - 1) +} + +func timestampGetDayOfMonthZeroBased(ts, tz ref.Val) ref.Val { + t, err := inTimeZone(ts, tz) + if err != nil { + return types.NewErrFromString(err.Error()) + } + return types.Int(t.Day() - 1) +} + +func timestampGetDayOfMonthOneBased(ts, tz ref.Val) ref.Val { + t, err := inTimeZone(ts, tz) + if err != nil { + return types.NewErrFromString(err.Error()) + } + return types.Int(t.Day()) +} + +func timestampGetDayOfWeek(ts, tz ref.Val) ref.Val { + t, err := inTimeZone(ts, tz) + if err != nil { + return types.NewErrFromString(err.Error()) + } + return types.Int(t.Weekday()) +} + +func timestampGetHours(ts, tz ref.Val) ref.Val { + t, err := inTimeZone(ts, tz) + if err != nil { + return types.NewErrFromString(err.Error()) + } + return types.Int(t.Hour()) +} + +func timestampGetMinutes(ts, tz ref.Val) ref.Val { + t, err := inTimeZone(ts, tz) + if err != nil { + return types.NewErrFromString(err.Error()) + } + return types.Int(t.Minute()) +} + +func timestampGetSeconds(ts, tz ref.Val) ref.Val { + t, err := inTimeZone(ts, tz) + if err != nil { + return types.NewErrFromString(err.Error()) + } + return types.Int(t.Second()) +} + +func timestampGetMilliseconds(ts, tz ref.Val) ref.Val { + t, err := inTimeZone(ts, tz) + if err != nil { + return types.NewErrFromString(err.Error()) + } + return types.Int(t.Nanosecond() / 1000000) +} + +func inTimeZone(ts, tz ref.Val) (time.Time, error) { + t := ts.(types.Timestamp) + val := string(tz.(types.String)) + ind := strings.Index(val, ":") + if ind == -1 { + loc, err := time.LoadLocation(val) + if err != nil { + return time.Time{}, err + } + return t.In(loc), nil + } + + // 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. + hr, err := strconv.Atoi(string(val[0:ind])) + if err != nil { + return time.Time{}, err + } + min, err := strconv.Atoi(string(val[ind+1:])) + if err != nil { + return time.Time{}, err + } + var offset int + if string(val[0]) == "-" { + offset = hr*60 - min + } else { + offset = hr*60 + min + } + secondsEastOfUTC := int((time.Duration(offset) * time.Minute).Seconds()) + timezone := time.FixedZone("", secondsEastOfUTC) + return t.In(timezone), nil +} 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 8f010fae443bc..37d4df4954385 100644 --- a/vendor/github.com/google/cel-go/common/types/BUILD.bazel +++ b/vendor/github.com/google/cel-go/common/types/BUILD.bazel @@ -18,6 +18,7 @@ go_library( "int.go", "iterator.go", "json_value.go", + "format.go", "list.go", "map.go", "null.go", @@ -39,7 +40,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", 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 565734f3ff5be..5f1e4573e1a04 100644 --- a/vendor/github.com/google/cel-go/common/types/bool.go +++ b/vendor/github.com/google/cel-go/common/types/bool.go @@ -18,6 +18,7 @@ import ( "fmt" "reflect" "strconv" + "strings" "github.com/google/cel-go/common/types/ref" @@ -68,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: @@ -128,6 +129,14 @@ func (b Bool) Value() any { return bool(b) } +func (b Bool) format(sb *strings.Builder) { + if b { + sb.WriteString("true") + } else { + sb.WriteString("false") + } +} + // IsBool returns whether the input ref.Val or ref.Type is equal to BoolType. func IsBool(elem ref.Val) bool { switch v := elem.(type) { 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 7e813e291bc39..2eefb5d7f7fc9 100644 --- a/vendor/github.com/google/cel-go/common/types/bytes.go +++ b/vendor/github.com/google/cel-go/common/types/bytes.go @@ -19,6 +19,7 @@ import ( "encoding/base64" "fmt" "reflect" + "strings" "unicode/utf8" "github.com/google/cel-go/common/types/ref" @@ -43,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. @@ -78,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. @@ -138,3 +142,17 @@ func (b Bytes) Type() ref.Type { func (b Bytes) Value() any { return []byte(b) } + +func (b Bytes) format(sb *strings.Builder) { + fmt.Fprintf(sb, "b\"%s\"", bytesToOctets([]byte(b))) +} + +// bytesToOctets converts byte sequences to a string using a three digit octal encoded value +// per byte. +func bytesToOctets(byteVal []byte) string { + var b strings.Builder + for _, c := range byteVal { + fmt.Fprintf(&b, "\\%03o", c) + } + return b.String() +} 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 027e789786b96..02abfee2dcf5c 100644 --- a/vendor/github.com/google/cel-go/common/types/double.go +++ b/vendor/github.com/google/cel-go/common/types/double.go @@ -18,6 +18,8 @@ import ( "fmt" "math" "reflect" + "strconv" + "strings" "github.com/google/cel-go/common/types/ref" @@ -87,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 @@ -209,3 +211,23 @@ func (d Double) Type() ref.Type { func (d Double) Value() any { return float64(d) } + +func (d Double) format(sb *strings.Builder) { + if math.IsNaN(float64(d)) { + sb.WriteString(`double("NaN")`) + return + } + if math.IsInf(float64(d), -1) { + sb.WriteString(`double("-Infinity")`) + return + } + if math.IsInf(float64(d), 1) { + sb.WriteString(`double("Infinity")`) + return + } + s := strconv.FormatFloat(float64(d), 'f', -1, 64) + sb.WriteString(s) + if !strings.ContainsRune(s, '.') { + sb.WriteString(".0") + } +} 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 596e56d6b03e2..2207147734de5 100644 --- a/vendor/github.com/google/cel-go/common/types/duration.go +++ b/vendor/github.com/google/cel-go/common/types/duration.go @@ -18,6 +18,7 @@ import ( "fmt" "reflect" "strconv" + "strings" "time" "github.com/google/cel-go/common/overloads" @@ -105,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) @@ -185,6 +186,10 @@ func (d Duration) Value() any { return d.Duration } +func (d Duration) format(sb *strings.Builder) { + fmt.Fprintf(sb, `duration("%ss")`, strconv.FormatFloat(d.Seconds(), 'f', -1, 64)) +} + // DurationGetHours returns the duration in hours. func DurationGetHours(val ref.Val) ref.Val { dur, ok := val.(Duration) 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 17ab1a95e5331..3216ff1c4af58 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/format.go b/vendor/github.com/google/cel-go/common/types/format.go new file mode 100644 index 0000000000000..174a2bd040565 --- /dev/null +++ b/vendor/github.com/google/cel-go/common/types/format.go @@ -0,0 +1,42 @@ +package types + +import ( + "fmt" + "strings" + + "github.com/google/cel-go/common/types/ref" + "github.com/google/cel-go/common/types/traits" +) + +type formattable interface { + format(*strings.Builder) +} + +// Format formats the value as a string. The result is only intended for human consumption and ignores errors. +// Do not depend on the output being stable. It may change at any time. +func Format(val ref.Val) string { + var sb strings.Builder + formatTo(&sb, val) + return sb.String() +} + +func formatTo(sb *strings.Builder, val ref.Val) { + if fmtable, ok := val.(formattable); ok { + fmtable.format(sb) + return + } + // All of the builtins implement formattable. Try to deal with traits. + if l, ok := val.(traits.Lister); ok { + formatList(l, sb) + return + } + if m, ok := val.(traits.Mapper); ok { + formatMap(m, sb) + return + } + // This could be an error, unknown, opaque or object. + // Unfortunately we have no consistent way of inspecting + // opaque and object. So we just fallback to fmt.Stringer + // and hope it is relavent. + fmt.Fprintf(sb, "%s", val) +} 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 0ae9507c39603..60d5a7160605b 100644 --- a/vendor/github.com/google/cel-go/common/types/int.go +++ b/vendor/github.com/google/cel-go/common/types/int.go @@ -19,6 +19,7 @@ import ( "math" "reflect" "strconv" + "strings" "time" "github.com/google/cel-go/common/types/ref" @@ -119,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. @@ -290,6 +291,10 @@ func (i Int) Value() any { return int64(i) } +func (i Int) format(sb *strings.Builder) { + sb.WriteString(strconv.FormatInt(int64(i), 10)) +} + // isJSONSafe indicates whether the int is safely representable as a floating point value in JSON. func (i Int) isJSONSafe() bool { return i >= minIntJSON && i <= maxIntJSON 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 13a4efe7ada77..90acfe7df3a0a 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 7e68a5daf3ba6..028770ed683d2 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 @@ -299,6 +293,22 @@ func (l *baseList) String() string { return sb.String() } +func formatList(l traits.Lister, sb *strings.Builder) { + sb.WriteString("[") + n, _ := l.Size().(Int) + for i := 0; i < int(n); i++ { + formatTo(sb, l.Get(Int(i))) + if i != int(n)-1 { + sb.WriteString(", ") + } + } + sb.WriteString("]") +} + +func (l *baseList) format(sb *strings.Builder) { + formatList(l, sb) +} + // mutableList aggregates values into its internal storage. For use with internal CEL variables only. type mutableList struct { *baseList @@ -334,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. @@ -345,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. @@ -458,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 cb6cce78b0875..e4d6f76574561 100644 --- a/vendor/github.com/google/cel-go/common/types/map.go +++ b/vendor/github.com/google/cel-go/common/types/map.go @@ -17,9 +17,10 @@ package types import ( "fmt" "reflect" + "sort" "strings" + "unicode" - "github.com/stoewer/go-strcase" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" @@ -155,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 } @@ -163,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 @@ -225,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) @@ -318,6 +322,41 @@ func (m *baseMap) String() string { return sb.String() } +type baseMapEntry struct { + key string + val string +} + +func formatMap(m traits.Mapper, sb *strings.Builder) { + it := m.Iterator() + var ents []baseMapEntry + if s, ok := m.Size().(Int); ok { + ents = make([]baseMapEntry, 0, int(s)) + } + for it.HasNext() == True { + k := it.Next() + v, _ := m.Find(k) + ents = append(ents, baseMapEntry{Format(k), Format(v)}) + } + sort.SliceStable(ents, func(i, j int) bool { + return ents[i].key < ents[j].key + }) + sb.WriteString("{") + for i, ent := range ents { + if i > 0 { + sb.WriteString(", ") + } + sb.WriteString(ent.key) + sb.WriteString(": ") + sb.WriteString(ent.val) + } + sb.WriteString("}") +} + +func (m *baseMap) format(sb *strings.Builder) { + formatMap(m, sb) +} + // Type implements the ref.Val interface method. func (m *baseMap) Type() ref.Type { return MapType @@ -667,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 { @@ -680,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 @@ -1000,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/null.go b/vendor/github.com/google/cel-go/common/types/null.go index 36514ff200782..671e1ee5c02c1 100644 --- a/vendor/github.com/google/cel-go/common/types/null.go +++ b/vendor/github.com/google/cel-go/common/types/null.go @@ -17,6 +17,7 @@ package types import ( "fmt" "reflect" + "strings" "google.golang.org/protobuf/proto" @@ -44,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 @@ -54,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) { @@ -117,3 +118,7 @@ func (n Null) Type() ref.Type { func (n Null) Value() any { return structpb.NullValue_NULL_VALUE } + +func (n Null) format(sb *strings.Builder) { + sb.WriteString("null") +} 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 5377bff8decc3..bb2a09e879f69 100644 --- a/vendor/github.com/google/cel-go/common/types/object.go +++ b/vendor/github.com/google/cel-go/common/types/object.go @@ -17,9 +17,12 @@ package types import ( "fmt" "reflect" + "sort" + "strings" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" "github.com/google/cel-go/common/types/pb" "github.com/google/cel-go/common/types/ref" @@ -68,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) @@ -163,3 +166,35 @@ func (o *protoObj) Type() ref.Type { func (o *protoObj) Value() any { return o.value } + +type protoObjField struct { + fd protoreflect.FieldDescriptor + v protoreflect.Value +} + +func (o *protoObj) format(sb *strings.Builder) { + var fields []protoreflect.FieldDescriptor + o.value.ProtoReflect().Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { + fields = append(fields, fd) + return true + }) + sort.SliceStable(fields, func(i, j int) bool { + return fields[i].Number() < fields[j].Number() + }) + sb.WriteString(o.Type().TypeName()) + sb.WriteString("{") + for i, field := range fields { + if i > 0 { + sb.WriteString(", ") + } + 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 97845a740c89d..0d861823d27a4 100644 --- a/vendor/github.com/google/cel-go/common/types/optional.go +++ b/vendor/github.com/google/cel-go/common/types/optional.go @@ -18,13 +18,14 @@ import ( "errors" "fmt" "reflect" + "strings" "github.com/google/cel-go/common/types/ref" ) 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{} @@ -58,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) } @@ -94,6 +98,16 @@ func (o *Optional) String() string { return "optional.none()" } +func (o *Optional) format(sb *strings.Builder) { + if o.HasValue() { + sb.WriteString(`optional.of(`) + formatTo(sb, o.GetValue()) + sb.WriteString(`)`) + } else { + sb.WriteString("optional.none()") + } +} + // Type implements the ref.Val interface method. func (o *Optional) Type() ref.Type { return OptionalType 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 e323afb1df305..3a8bdf0b24bfd 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 eadebcb04e8b7..c6fdfc6957208 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 bdd474c95af9a..8d7d1b299f5a1 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 936a4e28b206d..1bb2c11ed7d2d 100644 --- a/vendor/github.com/google/cel-go/common/types/provider.go +++ b/vendor/github.com/google/cel-go/common/types/provider.go @@ -81,6 +81,9 @@ 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. @@ -93,11 +96,40 @@ type Registry struct { // 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{ + return NewProtoRegistry(ProtoTypeDefs(types...)) +} + +// 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 + } +} + +// 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 + } +} + +// NewProtoRegistry creates a proto-based registry with a set of configurable options. +func NewProtoRegistry(opts ...RegistryOption) (*Registry, error) { + r := &Registry{ revTypeMap: make(map[string]*Type), pbdb: pb.NewDb(), } - err := p.RegisterType( + err := r.RegisterType( BoolType, BytesType, DoubleType, @@ -114,19 +146,19 @@ 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. @@ -149,6 +181,28 @@ func (p *Registry) Copy() *Registry { return copy } +// 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 + } + newDB := pb.NewDb(pb.JSONFieldNames(enabled)) + files := p.pbdb.FileDescriptions() + for _, fd := range files { + _, err := newDB.RegisterDescriptor(fd.FileDescriptor()) + if err != nil { + return err + } + } + 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) @@ -172,9 +226,11 @@ func (p *Registry) FindFieldType(structType, fieldName string) (*ref.FieldType, return nil, false } return &ref.FieldType{ - Type: field.CheckedType(), - IsSet: field.IsSet, - GetFrom: field.GetFrom}, true + Type: field.CheckedType(), + IsSet: field.IsSet, + GetFrom: field.GetFrom, + IsJSONField: p.pbdb.JSONFieldNames() && fieldName == field.JSONName(), + }, true } // FindStructFieldNames returns the set of field names for the given struct type, @@ -206,9 +262,25 @@ func (p *Registry) FindStructFieldType(structType, fieldName string) (*FieldType return nil, false } return &FieldType{ - Type: fieldDescToCELType(field), - IsSet: field.IsSet, - GetFrom: field.GetFrom}, true + Type: fieldDescToCELType(field), + IsSet: field.IsSet, + GetFrom: field.GetFrom, + IsJSONField: p.pbdb.JSONFieldNames() && fieldName == field.JSONName(), + }, true +} + +// 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) { + msgType, found := p.pbdb.DescribeType(structType) + if !found { + return "", false + } + field, found := msgType.FieldByName(fieldName) + if !found { + return "", false + } + return field.Documentation(), true } // FindIdent takes a qualified identifier name and returns a ref.Val if one exists. @@ -268,9 +340,8 @@ func (p *Registry) NewValue(structType string, fields map[string]ref.Val) ref.Va 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) } 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 b9820023d6f51..ed5ab06625545 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/string.go b/vendor/github.com/google/cel-go/common/types/string.go index 3a93743f24e25..1335903a7ba2a 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 } @@ -186,6 +190,10 @@ func (s String) Value() any { return string(s) } +func (s String) format(sb *strings.Builder) { + sb.WriteString(strconv.Quote(string(s))) +} + // StringContains returns whether the string contains a substring. func StringContains(s, sub ref.Val) ref.Val { str, ok := s.(String) 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 33acdea8ef7c9..62a020d970faf 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) @@ -179,6 +254,10 @@ func (t Timestamp) Value() any { return t.Time } +func (t Timestamp) format(sb *strings.Builder) { + fmt.Fprintf(sb, `timestamp("%s")`, t.Time.UTC().Format(time.RFC3339Nano)) +} + var ( timestampValueType = reflect.TypeOf(&tpb.Timestamp{}) @@ -298,6 +377,9 @@ func timeZone(tz ref.Val, visitor timestampVisitor) timestampVisitor { if err != nil { return WrapErr(err) } + 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/types.go b/vendor/github.com/google/cel-go/common/types/types.go index f419beabd03d6..78c77a9b5d36f 100644 --- a/vendor/github.com/google/cel-go/common/types/types.go +++ b/vendor/github.com/google/cel-go/common/types/types.go @@ -164,9 +164,9 @@ var ( traits.SubtractorType, } // ListType represents the runtime list type. - ListType = NewListType(nil) + ListType = NewListType(DynType) // MapType represents the runtime map type. - MapType = NewMapType(nil, nil) + MapType = NewMapType(DynType, DynType) // NullType represents the type of a null value. NullType = &Type{ kind: NullTypeKind, @@ -376,6 +376,10 @@ func (t *Type) TypeName() string { return t.runtimeTypeName } +func (t *Type) format(sb *strings.Builder) { + sb.WriteString(t.TypeName()) +} + // WithTraits creates a copy of the current Type and sets the trait mask to the traits parameter. // // This method should be used with Opaque types where the type acts like a container, e.g. vector. @@ -395,6 +399,9 @@ func (t *Type) WithTraits(traits int) *Type { // String returns a human-readable definition of the type name. func (t *Type) String() string { + if t.Kind() == TypeParamKind { + return fmt.Sprintf("<%s>", t.DeclaredTypeName()) + } if len(t.Parameters()) == 0 { return t.DeclaredTypeName() } 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 6d74f30d884d4..91d5369daae91 100644 --- a/vendor/github.com/google/cel-go/common/types/uint.go +++ b/vendor/github.com/google/cel-go/common/types/uint.go @@ -19,6 +19,7 @@ import ( "math" "reflect" "strconv" + "strings" "github.com/google/cel-go/common/types/ref" @@ -99,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 @@ -250,6 +251,11 @@ func (i Uint) Value() any { return uint64(i) } +func (i Uint) format(sb *strings.Builder) { + sb.WriteString(strconv.FormatUint(uint64(i), 10)) + sb.WriteString("u") +} + // isJSONSafe indicates whether the uint is safely representable as a floating point value in JSON. func (i Uint) isJSONSafe() bool { return i <= maxIntJSON 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 9dd2b25794798..f43aff18e0f73 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 b764fa1f5cc5a..f362fd97b8f90 100644 --- a/vendor/github.com/google/cel-go/ext/BUILD.bazel +++ b/vendor/github.com/google/cel-go/ext/BUILD.bazel @@ -9,13 +9,18 @@ go_library( srcs = [ "bindings.go", "comprehensions.go", + "costs.go", "encoders.go", + "extension_option_factory.go", "formatting.go", + "formatting_v2.go", "guards.go", "lists.go", "math.go", "native.go", + "network.go", "protos.go", + "regex.go", "sets.go", "strings.go", ], @@ -24,16 +29,19 @@ go_library( deps = [ "//cel:go_default_library", "//checker:go_default_library", + "//common:go_default_library", "//common/ast:go_default_library", "//common/decls:go_default_library", - "//common/overloads:go_default_library", + "//common/env:go_default_library", "//common/operators:go_default_library", + "//common/overloads:go_default_library", "//common/types:go_default_library", "//common/types/pb:go_default_library", "//common/types/ref:go_default_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", @@ -48,11 +56,16 @@ go_test( srcs = [ "bindings_test.go", "comprehensions_test.go", - "encoders_test.go", + "encoders_test.go", + "extension_option_factory_test.go", + "formatting_test.go", + "formatting_v2_test.go", "lists_test.go", "math_test.go", "native_test.go", + "network_test.go", "protos_test.go", + "regex_test.go", "sets_test.go", "strings_test.go", ], @@ -62,14 +75,16 @@ go_test( deps = [ "//cel:go_default_library", "//checker:go_default_library", + "//common:go_default_library", + "//common/env:go_default_library", "//common/types:go_default_library", "//common/types/ref:go_default_library", "//common/types/traits:go_default_library", "//test:go_default_library", "//test/proto2pb:go_default_library", "//test/proto3pb: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/wrapperspb:go_default_library", - "@org_golang_google_protobuf//encoding/protojson:go_default_library", ], ) diff --git a/vendor/github.com/google/cel-go/ext/README.md b/vendor/github.com/google/cel-go/ext/README.md index 4620204fc2922..6133b5cbf34e7 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 @@ -356,6 +378,23 @@ Examples: math.isFinite(0.0/0.0) // returns false math.isFinite(1.2) // returns true +### Math.Sqrt + +Introduced at version: 2 + +Returns the square root of the given input as double +Throws error for negative or non-numeric inputs + + math.sqrt() -> + math.sqrt() -> + math.sqrt() -> + +Examples: + + math.sqrt(81) // returns 9.0 + math.sqrt(985.25) // returns 31.388692231439016 + math.sqrt(-15) // returns NaN + ## Protos Protos configure extended macros and functions for proto manipulation. @@ -395,7 +434,7 @@ zero-based. ### Distinct -**Introduced in version 2** +**Introduced in version 2 (cost support in version 3)** Returns the distinct elements of a list. @@ -409,7 +448,7 @@ Examples: ### Flatten -**Introduced in version 1** +**Introduced in version 1 (cost support in version 3)** Flattens a list recursively. If an optional depth is provided, the list is flattened to a the specificied level. @@ -428,7 +467,7 @@ Examples: ### Range -**Introduced in version 2** +**Introduced in version 2 (cost support in version 3)** Returns a list of integers from 0 to n-1. @@ -441,7 +480,7 @@ Examples: ### Reverse -**Introduced in version 2** +**Introduced in version 2 (cost support in version 3)** Returns the elements of a list in reverse order. @@ -454,6 +493,7 @@ Examples: ### Slice +**Introduced in version 0 (cost support in version 3)** Returns a new sub-list using the indexes provided. @@ -466,7 +506,7 @@ Examples: ### Sort -**Introduced in version 2** +**Introduced in version 2 (cost support in version 3)** Sorts a list with comparable elements. If the element type is not comparable or the element types are not the same, the function will produce an error. @@ -483,7 +523,7 @@ Examples: ### SortBy -**Introduced in version 2** +**Introduced in version 2 (cost support in version 3)** Sorts a list by a key value, i.e., the order is determined by the result of an expression applied to each element of the list. @@ -913,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: @@ -927,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 50cf4fb3d65d7..89766d60aa9e7 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{} } @@ -149,7 +149,7 @@ type blockValidationExemption struct{} // Name returns the name of the validator. func (blockValidationExemption) Name() string { - return "cel.lib.ext.validate.functions.cel.block" + return "cel.validator.cel_block" } // Configure implements the ASTValidatorConfigurer interface and augments the list of functions to skip @@ -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 interpreter.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) { @@ -242,12 +253,18 @@ type slotVal struct { } type dynamicSlotActivation struct { - interpreter.Activation - slotExprs []interpreter.Interpretable + cel.Activation + 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 interpreter.Activation) ref.Val { - vars := constantSlotActivation{Activation: activation, slots: b.slots, slotCount: b.slotCount} - return b.expr.Eval(vars) +func (b *constantBlock) Eval(activation cel.Activation) ref.Val { + return b.Exec(interpreter.AsFrame(activation)) } type constantSlotActivation struct { - interpreter.Activation + 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 f08d8f9da68eb..adb22912b11af 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 0000000000000..d2cf7c757f8fc --- /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 731c3d095dccf..97fc932a57451 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 new file mode 100644 index 0000000000000..e68cf5bc7738a --- /dev/null +++ b/vendor/github.com/google/cel-go/ext/extension_option_factory.go @@ -0,0 +1,92 @@ +// 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 +// +// https://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" + + "github.com/google/cel-go/cel" + "github.com/google/cel-go/common/env" +) + +// ExtensionOptionFactory converts an ExtensionConfig value to a CEL environment option. +func ExtensionOptionFactory(configElement any) (cel.EnvOption, bool) { + ext, isExtension := configElement.(*env.Extension) + if !isExtension { + return nil, false + } + name := ext.Name + alias, found := extAliases[name] + if found { + name = alias + } + fac, found := extFactories[name] + if !found { + return nil, false + } + // If the version is 'latest', set the version value to the max uint. + ver, err := ext.VersionNumber() + if err != nil { + return func(*cel.Env) (*cel.Env, error) { + return nil, fmt.Errorf("invalid extension version: %s - %s", ext.Name, ext.Version) + }, true + } + return fac(ver), true +} + +// extensionFactory accepts a version and produces a CEL environment associated with the versioned extension. +type extensionFactory func(uint32) cel.EnvOption + +var extFactories = map[string]extensionFactory{ + "cel.lib.ext.cel.bindings": func(version uint32) cel.EnvOption { + return Bindings(BindingsVersion(version)) + }, + "cel.lib.ext.encoders": func(version uint32) cel.EnvOption { + return Encoders(EncodersVersion(version)) + }, + "cel.lib.ext.lists": func(version uint32) cel.EnvOption { + return Lists(ListsVersion(version)) + }, + "cel.lib.ext.math": func(version uint32) cel.EnvOption { + return Math(MathVersion(version)) + }, + "cel.lib.ext.protos": func(version uint32) cel.EnvOption { + return Protos(ProtosVersion(version)) + }, + "cel.lib.ext.sets": func(version uint32) cel.EnvOption { + return Sets(SetsVersion(version)) + }, + "cel.lib.ext.strings": func(version uint32) cel.EnvOption { + return Strings(StringsVersion(version)) + }, + "cel.lib.ext.comprev2": func(version uint32) cel.EnvOption { + return TwoVarComprehensions(TwoVarComprehensionsVersion(version)) + }, + "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 932d562ec9eef..35fb17048ef3d 100644 --- a/vendor/github.com/google/cel-go/ext/formatting.go +++ b/vendor/github.com/google/cel-go/ext/formatting.go @@ -268,14 +268,17 @@ func makeMatcher(locale string) (language.Matcher, error) { type stringFormatter struct{} +// String implements formatStringInterpolator.String. func (c *stringFormatter) String(arg ref.Val, locale string) (string, error) { return FormatString(arg, locale) } +// Decimal implements formatStringInterpolator.Decimal. func (c *stringFormatter) Decimal(arg ref.Val, locale string) (string, error) { return formatDecimal(arg, locale) } +// Fixed implements formatStringInterpolator.Fixed. func (c *stringFormatter) Fixed(precision *int) func(ref.Val, string) (string, error) { if precision == nil { precision = new(int) @@ -307,6 +310,7 @@ func (c *stringFormatter) Fixed(precision *int) func(ref.Val, string) (string, e } } +// Scientific implements formatStringInterpolator.Scientific. func (c *stringFormatter) Scientific(precision *int) func(ref.Val, string) (string, error) { if precision == nil { precision = new(int) @@ -337,6 +341,7 @@ func (c *stringFormatter) Scientific(precision *int) func(ref.Val, string) (stri } } +// Binary implements formatStringInterpolator.Binary. func (c *stringFormatter) Binary(arg ref.Val, locale string) (string, error) { switch arg.Type() { case types.IntType: @@ -358,6 +363,7 @@ func (c *stringFormatter) Binary(arg ref.Val, locale string) (string, error) { } } +// Hex implements formatStringInterpolator.Hex. func (c *stringFormatter) Hex(useUpper bool) func(ref.Val, string) (string, error) { return func(arg ref.Val, locale string) (string, error) { fmtStr := "%x" @@ -388,6 +394,7 @@ func (c *stringFormatter) Hex(useUpper bool) func(ref.Val, string) (string, erro } } +// Octal implements formatStringInterpolator.Octal. func (c *stringFormatter) Octal(arg ref.Val, locale string) (string, error) { switch arg.Type() { case types.IntType: @@ -403,11 +410,13 @@ 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 { - return "cel.lib.ext.validate.functions.string.format" + return "cel.validator.string_format" } // Configure implements the ASTValidatorConfigurer interface and augments the list of functions to skip @@ -420,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 { @@ -432,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 @@ -504,6 +513,7 @@ type stringFormatChecker struct { ast *ast.AST } +// String implements formatStringInterpolator.String. func (c *stringFormatChecker) String(arg ref.Val, locale string) (string, error) { formatArg := c.args[c.currArgIndex] valid, badID := c.verifyString(formatArg) @@ -513,6 +523,7 @@ func (c *stringFormatChecker) String(arg ref.Val, locale string) (string, error) return "", nil } +// Decimal implements formatStringInterpolator.Decimal. func (c *stringFormatChecker) Decimal(arg ref.Val, locale string) (string, error) { id := c.args[c.currArgIndex].ID() valid := c.verifyTypeOneOf(id, types.IntType, types.UintType) @@ -522,6 +533,7 @@ func (c *stringFormatChecker) Decimal(arg ref.Val, locale string) (string, error return "", nil } +// Fixed implements formatStringInterpolator.Fixed. func (c *stringFormatChecker) Fixed(precision *int) func(ref.Val, string) (string, error) { return func(arg ref.Val, locale string) (string, error) { id := c.args[c.currArgIndex].ID() @@ -534,6 +546,7 @@ func (c *stringFormatChecker) Fixed(precision *int) func(ref.Val, string) (strin } } +// Scientific implements formatStringInterpolator.Scientific. func (c *stringFormatChecker) Scientific(precision *int) func(ref.Val, string) (string, error) { return func(arg ref.Val, locale string) (string, error) { id := c.args[c.currArgIndex].ID() @@ -545,6 +558,7 @@ func (c *stringFormatChecker) Scientific(precision *int) func(ref.Val, string) ( } } +// Binary implements formatStringInterpolator.Binary. func (c *stringFormatChecker) Binary(arg ref.Val, locale string) (string, error) { id := c.args[c.currArgIndex].ID() valid := c.verifyTypeOneOf(id, types.IntType, types.UintType, types.BoolType) @@ -554,6 +568,7 @@ func (c *stringFormatChecker) Binary(arg ref.Val, locale string) (string, error) return "", nil } +// Hex implements formatStringInterpolator.Hex. func (c *stringFormatChecker) Hex(useUpper bool) func(ref.Val, string) (string, error) { return func(arg ref.Val, locale string) (string, error) { id := c.args[c.currArgIndex].ID() @@ -565,6 +580,7 @@ func (c *stringFormatChecker) Hex(useUpper bool) func(ref.Val, string) (string, } } +// Octal implements formatStringInterpolator.Octal. func (c *stringFormatChecker) Octal(arg ref.Val, locale string) (string, error) { id := c.args[c.currArgIndex].ID() valid := c.verifyTypeOneOf(id, types.IntType, types.UintType) @@ -574,6 +590,7 @@ func (c *stringFormatChecker) Octal(arg ref.Val, locale string) (string, error) return "", nil } +// Arg implements formatListArgs.Arg. func (c *stringFormatChecker) Arg(index int64) (ref.Val, error) { c.argsRequested++ c.currArgIndex = index @@ -582,6 +599,7 @@ func (c *stringFormatChecker) Arg(index int64) (ref.Val, error) { return types.Int(0), nil } +// Size implements formatListArgs.Size. func (c *stringFormatChecker) Size() int64 { return int64(len(c.args)) } @@ -686,10 +704,12 @@ func newFormatError(id int64, msg string, args ...any) error { } } +// Error implements error. func (e formatError) Error() string { return e.msg } +// Is implements errors.Is. func (e formatError) Is(target error) bool { return e.msg == target.Error() } @@ -699,6 +719,7 @@ type stringArgList struct { args traits.Lister } +// Arg implements formatListArgs.Arg. func (c *stringArgList) Arg(index int64) (ref.Val, error) { if index >= c.args.Size().Value().(int64) { return nil, fmt.Errorf("index %d out of range", index) @@ -706,6 +727,7 @@ func (c *stringArgList) Arg(index int64) (ref.Val, error) { return c.args.Get(types.Int(index)), nil } +// Size implements formatListArgs.Size. func (c *stringArgList) Size() int64 { return c.args.Size().Value().(int64) } @@ -758,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 @@ -782,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 } @@ -806,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) @@ -821,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) @@ -850,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 @@ -871,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 } @@ -887,14 +912,17 @@ func newParseFormatError(msg string, wrapped error) error { return parseFormatError{msg: msg, wrapped: wrapped} } +// Error implements error. func (e parseFormatError) Error() string { return fmt.Sprintf("%s: %s", e.msg, e.wrapped.Error()) } +// Is implements errors.Is. func (e parseFormatError) Is(target error) bool { return e.Error() == target.Error() } +// Is implements errors.Unwrap. func (e parseFormatError) Unwrap() error { return e.wrapped } diff --git a/vendor/github.com/google/cel-go/ext/formatting_v2.go b/vendor/github.com/google/cel-go/ext/formatting_v2.go new file mode 100644 index 0000000000000..f923cc7e1038e --- /dev/null +++ b/vendor/github.com/google/cel-go/ext/formatting_v2.go @@ -0,0 +1,793 @@ +// Copyright 2023 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 ( + "errors" + "fmt" + "math" + "sort" + "strconv" + "strings" + "time" + "unicode" + + "github.com/google/cel-go/cel" + "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" +) + +type clauseImplV2 func(ref.Val) (string, error) + +type appendingFormatterV2 struct { + buf []byte +} + +type formattedMapEntryV2 struct { + key string + val string +} + +func (af *appendingFormatterV2) format(arg ref.Val) error { + switch arg.Type() { + case types.BoolType: + argBool, ok := arg.Value().(bool) + if !ok { + return fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.BoolType) + } + af.buf = strconv.AppendBool(af.buf, argBool) + return nil + case types.IntType: + argInt, ok := arg.Value().(int64) + if !ok { + return fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.IntType) + } + af.buf = strconv.AppendInt(af.buf, argInt, 10) + return 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) + } + af.buf = strconv.AppendUint(af.buf, argUint, 10) + return nil + case types.DoubleType: + argDbl, ok := arg.Value().(float64) + if !ok { + return fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.DoubleType) + } + if math.IsNaN(argDbl) { + af.buf = append(af.buf, "NaN"...) + return nil + } + if math.IsInf(argDbl, -1) { + af.buf = append(af.buf, "-Infinity"...) + return nil + } + if math.IsInf(argDbl, 1) { + af.buf = append(af.buf, "Infinity"...) + return nil + } + af.buf = strconv.AppendFloat(af.buf, argDbl, 'f', -1, 64) + return nil + case types.BytesType: + argBytes, ok := arg.Value().([]byte) + if !ok { + return fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.BytesType) + } + af.buf = append(af.buf, argBytes...) + return nil + case types.StringType: + argStr, ok := arg.Value().(string) + if !ok { + return fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.StringType) + } + af.buf = append(af.buf, argStr...) + return nil + case types.DurationType: + argDur, ok := arg.Value().(time.Duration) + if !ok { + return fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.DurationType) + } + af.buf = strconv.AppendFloat(af.buf, argDur.Seconds(), 'f', -1, 64) + af.buf = append(af.buf, "s"...) + return nil + case types.TimestampType: + argTime, ok := arg.Value().(time.Time) + if !ok { + return fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.TimestampType) + } + af.buf = argTime.UTC().AppendFormat(af.buf, time.RFC3339Nano) + return nil + case types.NullType: + af.buf = append(af.buf, "null"...) + return nil + case types.TypeType: + argType, ok := arg.Value().(string) + if !ok { + return fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.TypeType) + } + af.buf = append(af.buf, argType...) + return nil + case types.ListType: + argList, ok := arg.(traits.Lister) + if !ok { + return fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.ListType) + } + argIter := argList.Iterator() + af.buf = append(af.buf, "["...) + if argIter.HasNext() == types.True { + if err := af.format(argIter.Next()); err != nil { + return err + } + for argIter.HasNext() == types.True { + af.buf = append(af.buf, ", "...) + if err := af.format(argIter.Next()); err != nil { + return err + } + } + } + af.buf = append(af.buf, "]"...) + return nil + case types.MapType: + argMap, ok := arg.(traits.Mapper) + if !ok { + return fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.MapType) + } + argIter := argMap.Iterator() + ents := []formattedMapEntryV2{} + for argIter.HasNext() == types.True { + key := argIter.Next() + val, ok := argMap.Find(key) + if !ok { + return fmt.Errorf("key missing from map: '%s'", key) + } + keyStr, err := formatStringV2(key) + if err != nil { + return err + } + valStr, err := formatStringV2(val) + if err != nil { + return err + } + ents = append(ents, formattedMapEntryV2{keyStr, valStr}) + } + sort.SliceStable(ents, func(x, y int) bool { + return ents[x].key < ents[y].key + }) + af.buf = append(af.buf, "{"...) + for i, e := range ents { + if i > 0 { + af.buf = append(af.buf, ", "...) + } + af.buf = append(af.buf, e.key...) + af.buf = append(af.buf, ": "...) + af.buf = append(af.buf, e.val...) + } + af.buf = append(af.buf, "}"...) + return nil + default: + return stringFormatErrorV2(runtimeID, arg.Type().TypeName()) + } +} + +func formatStringV2(arg ref.Val) (string, error) { + var fmter appendingFormatterV2 + if err := fmter.format(arg); err != nil { + return "", err + } + return string(fmter.buf), nil +} + +type stringFormatterV2 struct{} + +// String implements formatStringInterpolatorV2.String. +func (c *stringFormatterV2) String(arg ref.Val) (string, error) { + return formatStringV2(arg) +} + +// Decimal implements formatStringInterpolatorV2.Decimal. +func (c *stringFormatterV2) Decimal(arg ref.Val) (string, error) { + switch arg.Type() { + case types.IntType: + argInt, ok := arg.Value().(int64) + if !ok { + return "", fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.IntType) + } + return strconv.FormatInt(argInt, 10), 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 strconv.FormatUint(argUint, 10), nil + case types.DoubleType: + argDbl, ok := arg.Value().(float64) + if !ok { + return "", fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.DoubleType) + } + if math.IsNaN(argDbl) { + return "NaN", nil + } + if math.IsInf(argDbl, -1) { + return "-Infinity", nil + } + if math.IsInf(argDbl, 1) { + return "Infinity", nil + } + return strconv.FormatFloat(argDbl, 'f', -1, 64), nil + default: + return "", decimalFormatErrorV2(runtimeID, arg.Type().TypeName()) + } +} + +// Fixed implements formatStringInterpolatorV2.Fixed. +func (c *stringFormatterV2) Fixed(precision int) func(ref.Val) (string, error) { + return func(arg ref.Val) (string, error) { + fmtStr := fmt.Sprintf("%%.%df", precision) + switch arg.Type() { + case types.IntType: + argInt, ok := arg.Value().(int64) + if !ok { + return "", fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.IntType) + } + 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, float64(argUint)), nil + case types.DoubleType: + argDbl, ok := arg.Value().(float64) + if !ok { + return "", fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.DoubleType) + } + if math.IsNaN(argDbl) { + return "NaN", nil + } + if math.IsInf(argDbl, -1) { + return "-Infinity", nil + } + if math.IsInf(argDbl, 1) { + return "Infinity", nil + } + return fmt.Sprintf(fmtStr, argDbl), nil + default: + return "", fixedPointFormatErrorV2(runtimeID, arg.Type().TypeName()) + } + } +} + +// Scientific implements formatStringInterpolatorV2.Scientific. +func (c *stringFormatterV2) Scientific(precision int) func(ref.Val) (string, error) { + return func(arg ref.Val) (string, error) { + fmtStr := fmt.Sprintf("%%1.%de", precision) + switch arg.Type() { + case types.IntType: + argInt, ok := arg.Value().(int64) + if !ok { + return "", fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.IntType) + } + 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, float64(argUint)), nil + case types.DoubleType: + argDbl, ok := arg.Value().(float64) + if !ok { + return "", fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.DoubleType) + } + if math.IsNaN(argDbl) { + return "NaN", nil + } + if math.IsInf(argDbl, -1) { + return "-Infinity", nil + } + if math.IsInf(argDbl, 1) { + return "Infinity", nil + } + return fmt.Sprintf(fmtStr, argDbl), nil + default: + return "", scientificFormatErrorV2(runtimeID, arg.Type().TypeName()) + } + } +} + +// Binary implements formatStringInterpolatorV2.Binary. +func (c *stringFormatterV2) Binary(arg ref.Val) (string, error) { + switch arg.Type() { + case types.BoolType: + argBool, ok := arg.Value().(bool) + if !ok { + return "", fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.BoolType) + } + if argBool { + return "1", nil + } + return "0", nil + case types.IntType: + argInt, ok := arg.Value().(int64) + if !ok { + return "", fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.IntType) + } + return strconv.FormatInt(argInt, 2), 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 strconv.FormatUint(argUint, 2), nil + default: + return "", binaryFormatErrorV2(runtimeID, arg.Type().TypeName()) + } +} + +// Hex implements formatStringInterpolatorV2.Hex. +func (c *stringFormatterV2) Hex(useUpper bool) func(ref.Val) (string, error) { + return func(arg ref.Val) (string, error) { + var fmtStr string + if useUpper { + fmtStr = "%X" + } else { + fmtStr = "%x" + } + switch arg.Type() { + case types.IntType: + argInt, ok := arg.Value().(int64) + if !ok { + return "", fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.IntType) + } + return fmt.Sprintf(fmtStr, 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 + case types.StringType: + argStr, ok := arg.Value().(string) + if !ok { + return "", fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.StringType) + } + return fmt.Sprintf(fmtStr, argStr), nil + case types.BytesType: + argBytes, ok := arg.Value().([]byte) + if !ok { + return "", fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.BytesType) + } + return fmt.Sprintf(fmtStr, argBytes), nil + default: + return "", hexFormatErrorV2(runtimeID, arg.Type().TypeName()) + } + } +} + +// Octal implements formatStringInterpolatorV2.Octal. +func (c *stringFormatterV2) Octal(arg ref.Val) (string, error) { + switch arg.Type() { + case types.IntType: + argInt, ok := arg.Value().(int64) + if !ok { + return "", fmt.Errorf("type conversion error from '%s' to '%s'", arg.Type(), types.IntType) + } + return strconv.FormatInt(argInt, 8), 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 strconv.FormatUint(argUint, 8), nil + default: + return "", octalFormatErrorV2(runtimeID, arg.Type().TypeName()) + } +} + +// stringFormatValidatorV2 implements the cel.ASTValidator interface allowing for static validation +// of string.format calls. +type stringFormatValidatorV2 struct { + maxPrecision int +} + +// Name returns the name of the validator. +func (stringFormatValidatorV2) Name() string { + return "cel.validator.string_format" +} + +// Configure implements the ASTValidatorConfigurer interface and augments the list of functions to skip +// during homogeneous aggregate literal type-checks. +func (stringFormatValidatorV2) Configure(config cel.MutableValidatorConfig) error { + functions := config.GetOrDefault(cel.HomogeneousAggregateLiteralExemptFunctions, []string{}).([]string) + functions = append(functions, "format") + return config.Set(cel.HomogeneousAggregateLiteralExemptFunctions, functions) +} + +// 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 (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 { + call := e.AsCall() + formatStr := call.Target().AsLiteral().Value().(string) + args := call.Args()[0].AsList().Elements() + formatCheck := &stringFormatCheckerV2{ + args: args, + ast: a, + } + // use a placeholder locale, since locale doesn't affect syntax + _, err := parseFormatStringV2(formatStr, formatCheck, formatCheck, v.maxPrecision) + if err != nil { + iss.ReportErrorAtID(getErrorExprID(e.ID(), err), "%v", err) + continue + } + seenArgs := formatCheck.argsRequested + if len(args) > seenArgs { + iss.ReportErrorAtID(e.ID(), + "too many arguments supplied to string.format (expected %d, got %d)", seenArgs, len(args)) + } + } +} + +// stringFormatCheckerV2 implements the formatStringInterpolater interface +type stringFormatCheckerV2 struct { + args []ast.Expr + argsRequested int + currArgIndex int64 + ast *ast.AST +} + +// String implements formatStringInterpolatorV2.String. +func (c *stringFormatCheckerV2) String(arg ref.Val) (string, error) { + formatArg := c.args[c.currArgIndex] + valid, badID := c.verifyString(formatArg) + if !valid { + return "", stringFormatErrorV2(badID, c.typeOf(badID).TypeName()) + } + return "", nil +} + +// Decimal implements formatStringInterpolatorV2.Decimal. +func (c *stringFormatCheckerV2) Decimal(arg ref.Val) (string, error) { + id := c.args[c.currArgIndex].ID() + valid := c.verifyTypeOneOf(id, types.IntType, types.UintType, types.DoubleType) + if !valid { + return "", decimalFormatErrorV2(id, c.typeOf(id).TypeName()) + } + return "", nil +} + +// Fixed implements formatStringInterpolatorV2.Fixed. +func (c *stringFormatCheckerV2) Fixed(precision int) func(ref.Val) (string, error) { + return func(arg ref.Val) (string, error) { + id := c.args[c.currArgIndex].ID() + valid := c.verifyTypeOneOf(id, types.IntType, types.UintType, types.DoubleType) + if !valid { + return "", fixedPointFormatErrorV2(id, c.typeOf(id).TypeName()) + } + return "", nil + } +} + +// Scientific implements formatStringInterpolatorV2.Scientific. +func (c *stringFormatCheckerV2) Scientific(precision int) func(ref.Val) (string, error) { + return func(arg ref.Val) (string, error) { + id := c.args[c.currArgIndex].ID() + valid := c.verifyTypeOneOf(id, types.IntType, types.UintType, types.DoubleType) + if !valid { + return "", scientificFormatErrorV2(id, c.typeOf(id).TypeName()) + } + return "", nil + } +} + +// Binary implements formatStringInterpolatorV2.Binary. +func (c *stringFormatCheckerV2) Binary(arg ref.Val) (string, error) { + id := c.args[c.currArgIndex].ID() + valid := c.verifyTypeOneOf(id, types.BoolType, types.IntType, types.UintType) + if !valid { + return "", binaryFormatErrorV2(id, c.typeOf(id).TypeName()) + } + return "", nil +} + +// Hex implements formatStringInterpolatorV2.Hex. +func (c *stringFormatCheckerV2) Hex(useUpper bool) func(ref.Val) (string, error) { + return func(arg ref.Val) (string, error) { + id := c.args[c.currArgIndex].ID() + valid := c.verifyTypeOneOf(id, types.IntType, types.UintType, types.StringType, types.BytesType) + if !valid { + return "", hexFormatErrorV2(id, c.typeOf(id).TypeName()) + } + return "", nil + } +} + +// Octal implements formatStringInterpolatorV2.Octal. +func (c *stringFormatCheckerV2) Octal(arg ref.Val) (string, error) { + id := c.args[c.currArgIndex].ID() + valid := c.verifyTypeOneOf(id, types.IntType, types.UintType) + if !valid { + return "", octalFormatErrorV2(id, c.typeOf(id).TypeName()) + } + return "", nil +} + +// Arg implements formatListArgs.Arg. +func (c *stringFormatCheckerV2) Arg(index int64) (ref.Val, error) { + c.argsRequested++ + c.currArgIndex = index + // return a dummy value - this is immediately passed to back to us + // through one of the FormatCallback functions, so anything will do + return types.Int(0), nil +} + +// Size implements formatListArgs.Size. +func (c *stringFormatCheckerV2) Size() int64 { + return int64(len(c.args)) +} + +func (c *stringFormatCheckerV2) typeOf(id int64) *cel.Type { + return c.ast.GetType(id) +} + +func (c *stringFormatCheckerV2) verifyTypeOneOf(id int64, validTypes ...*cel.Type) bool { + t := c.typeOf(id) + if t == cel.DynType { + return true + } + for _, vt := range validTypes { + // Only check runtime type compatibility without delving deeper into parameterized types + if t.Kind() == vt.Kind() { + return true + } + } + return false +} + +func (c *stringFormatCheckerV2) verifyString(sub ast.Expr) (bool, int64) { + paramA := cel.TypeParamType("A") + paramB := cel.TypeParamType("B") + subVerified := c.verifyTypeOneOf(sub.ID(), + cel.ListType(paramA), cel.MapType(paramA, paramB), + cel.IntType, cel.UintType, cel.DoubleType, cel.BoolType, cel.StringType, + cel.TimestampType, cel.BytesType, cel.DurationType, cel.TypeType, cel.NullType) + if !subVerified { + return false, sub.ID() + } + switch sub.Kind() { + case ast.ListKind: + for _, e := range sub.AsList().Elements() { + // recursively verify if we're dealing with a list/map + verified, id := c.verifyString(e) + if !verified { + return false, id + } + } + return true, sub.ID() + case ast.MapKind: + for _, e := range sub.AsMap().Entries() { + // recursively verify if we're dealing with a list/map + entry := e.AsMapEntry() + verified, id := c.verifyString(entry.Key()) + if !verified { + return false, id + } + verified, id = c.verifyString(entry.Value()) + if !verified { + return false, id + } + } + return true, sub.ID() + default: + return true, sub.ID() + } +} + +// helper routines for reporting common errors during string formatting static validation and +// runtime execution. + +func binaryFormatErrorV2(id int64, badType string) error { + return newFormatError(id, "only ints, uints, and bools can be formatted as binary, was given %s", badType) +} + +func decimalFormatErrorV2(id int64, badType string) error { + return newFormatError(id, "decimal clause can only be used on ints, uints, and doubles, was given %s", badType) +} + +func fixedPointFormatErrorV2(id int64, badType string) error { + return newFormatError(id, "fixed-point clause can only be used on ints, uints, and doubles, was given %s", badType) +} + +func hexFormatErrorV2(id int64, badType string) error { + return newFormatError(id, "only ints, uints, bytes, and strings can be formatted as hex, was given %s", badType) +} + +func octalFormatErrorV2(id int64, badType string) error { + return newFormatError(id, "octal clause can only be used on ints and uints, was given %s", badType) +} + +func scientificFormatErrorV2(id int64, badType string) error { + return newFormatError(id, "scientific clause can only be used on ints, uints, and doubles, was given %s", badType) +} + +func stringFormatErrorV2(id int64, badType string) error { + return newFormatError(id, "string clause can only be used on strings, bools, bytes, ints, doubles, maps, lists, types, durations, and timestamps, was given %s", badType) +} + +// formatStringInterpolatorV2 is an interface that allows user-defined behavior +// for formatting clause implementations, as well as argument retrieval. +// Each function is expected to support the appropriate types as laid out in +// the string.format documentation, and to return an error if given an inappropriate type. +type formatStringInterpolatorV2 interface { + // String takes a ref.Val and a string representing the current locale identifier + // and returns the Val formatted as a string, or an error if one occurred. + String(ref.Val) (string, error) + + // Decimal takes a ref.Val and a string representing the current locale identifier + // and returns the Val formatted as a decimal integer, or an error if one occurred. + Decimal(ref.Val) (string, error) + + // Fixed takes an int pointer representing precision (or nil if none was given) and + // returns a function operating in a similar manner to String and Decimal, taking a + // ref.Val and locale and returning the appropriate string. A closure is returned + // so precision can be set without needing an additional function call/configuration. + Fixed(int) func(ref.Val) (string, error) + + // Scientific functions identically to Fixed, except the string returned from the closure + // is expected to be in scientific notation. + Scientific(int) func(ref.Val) (string, error) + + // Binary takes a ref.Val and a string representing the current locale identifier + // and returns the Val formatted as a binary integer, or an error if one occurred. + Binary(ref.Val) (string, error) + + // Hex takes a boolean that, if true, indicates the hex string output by the returned + // closure should use uppercase letters for A-F. + Hex(bool) func(ref.Val) (string, error) + + // Octal takes a ref.Val and a string representing the current locale identifier and + // returns the Val formatted in octal, or an error if one occurred. + Octal(ref.Val) (string, error) +} + +// 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, maxPrecision int) (string, error) { + i := 0 + argIndex := 0 + var builtStr strings.Builder + for i < len(formatStr) { + if formatStr[i] == '%' { + if i+1 < len(formatStr) && formatStr[i+1] == '%' { + err := builtStr.WriteByte('%') + if err != nil { + return "", fmt.Errorf("error writing format string: %w", err) + } + i += 2 + continue + } else { + argAny, err := list.Arg(int64(argIndex)) + if err != nil { + return "", err + } + if i+1 >= len(formatStr) { + return "", errors.New("unexpected end of string") + } + if int64(argIndex) >= list.Size() { + return "", fmt.Errorf("index %d out of range", argIndex) + } + numRead, val, refErr := parseAndFormatClauseV2(formatStr[i:], argAny, callback, list, maxPrecision) + if refErr != nil { + return "", refErr + } + _, err = builtStr.WriteString(val) + if err != nil { + return "", fmt.Errorf("error writing format string: %w", err) + } + i += numRead + argIndex++ + } + } else { + err := builtStr.WriteByte(formatStr[i]) + if err != nil { + return "", fmt.Errorf("error writing format string: %w", err) + } + i++ + } + } + return builtStr.String(), nil +} + +// 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, maxPrecision int) (int, string, error) { + i := 1 + read, formatter, err := parseFormattingClauseV2(formatStr[i:], callback, maxPrecision) + i += read + if err != nil { + return -1, "", newParseFormatError("could not parse formatting clause", err) + } + + valStr, err := formatter(val) + if err != nil { + return -1, "", newParseFormatError("error during formatting", err) + } + return i, valStr, nil +} + +func parseFormattingClauseV2(formatStr string, callback formatStringInterpolatorV2, maxPrecision int) (int, clauseImplV2, error) { + i := 0 + read, precision, err := parsePrecisionV2(formatStr[i:], maxPrecision) + i += read + if err != nil { + return -1, nil, fmt.Errorf("error while parsing precision: %w", err) + } + r := rune(formatStr[i]) + i++ + switch r { + case 's': + return i, callback.String, nil + case 'd': + return i, callback.Decimal, nil + case 'f': + return i, callback.Fixed(precision), nil + case 'e': + return i, callback.Scientific(precision), nil + case 'b': + return i, callback.Binary, nil + case 'x', 'X': + return i, callback.Hex(unicode.IsUpper(r)), nil + case 'o': + return i, callback.Octal, nil + default: + return -1, nil, fmt.Errorf("unrecognized formatting clause \"%c\"", r) + } +} + +func parsePrecisionV2(formatStr string, maxPrecision int) (int, int, error) { + i := 0 + if formatStr[i] != '.' { + return i, defaultPrecision, nil + } + i++ + var buffer strings.Builder + for { + if i >= len(formatStr) { + return -1, -1, errors.New("could not find end of precision specifier") + } + if !isASCIIDigit(rune(formatStr[i])) { + break + } + buffer.WriteByte(formatStr[i]) + i++ + } + precision, err := strconv.Atoi(buffer.String()) + if err != nil { + return -1, -1, fmt.Errorf("error while converting precision to integer: %w", err) + } + 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 675ea867281e4..3d0e676426bbd 100644 --- a/vendor/github.com/google/cel-go/ext/lists.go +++ b/vendor/github.com/google/cel-go/ext/lists.go @@ -20,11 +20,14 @@ import ( "sort" "github.com/google/cel-go/cel" + "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/decls" "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" "github.com/google/cel-go/parser" ) @@ -44,7 +47,7 @@ var comparableTypes = []*cel.Type{ // // # Distinct // -// Introduced in version: 2 +// Introduced in version: 2 (cost support in version 3) // // Returns the distinct elements of a list. // @@ -58,7 +61,7 @@ var comparableTypes = []*cel.Type{ // // # Range // -// Introduced in version: 2 +// Introduced in version: 2 (cost support in version 3) // // Returns a list of integers from 0 to n-1. // @@ -70,7 +73,7 @@ var comparableTypes = []*cel.Type{ // // # Reverse // -// Introduced in version: 2 +// Introduced in version: 2 (cost support in version 3) // // Returns the elements of a list in reverse order. // @@ -82,6 +85,8 @@ var comparableTypes = []*cel.Type{ // // # Slice // +// Introduced in version: 0 (cost support in version 3) +// // Returns a new sub-list using the indexes provided. // // .slice(, ) -> @@ -93,12 +98,14 @@ var comparableTypes = []*cel.Type{ // // # Flatten // +// Introduced in version: 1 (cost support in version 3) +// // Flattens a list recursively. -// If an optional depth is provided, the list is flattened to a the specificied level. +// If an optional depth is provided, the list is flattened to a the specified level. // A negative depth value will result in an error. // -// .flatten() -> -// .flatten(, ) -> +// .flatten() -> +// .flatten() -> // // Examples: // @@ -110,7 +117,7 @@ var comparableTypes = []*cel.Type{ // // # Sort // -// Introduced in version: 2 +// Introduced in version: 2 (cost support in version 3) // // Sorts a list with comparable elements. If the element type is not comparable // or the element types are not the same, the function will produce an error. @@ -127,6 +134,8 @@ var comparableTypes = []*cel.Type{ // // # SortBy // +// Introduced in version: 2 (cost support in version 3) +// // Sorts a list by a key value, i.e., the order is determined by the result of // an expression applied to each element of the list. // The output of the key expression must be a comparable type, otherwise the @@ -134,7 +143,7 @@ var comparableTypes = []*cel.Type{ // // .sortBy(, ) -> // keyExpr returns a value in {int, uint, double, bool, duration, timestamp, string, bytes} - +// // Examples: // // [ @@ -143,17 +152,19 @@ var comparableTypes = []*cel.Type{ // Player { name: "baz", score: 1000 }, // ].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. @@ -180,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 10,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")) @@ -301,12 +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.FunctionBinding(func(args ...ref.Val) ref.Val { - n := args[0].(types.Int) - result, err := genRange(n) + cel.UnaryBinding(func(n ref.Val) ref.Val { + result, err := genRange(n.(types.Int), maxRange) if err != nil { return types.WrapErr(err) } @@ -317,9 +338,8 @@ func (lib listsLib) CompileOptions() []cel.EnvOption { opts = append(opts, cel.Function("reverse", cel.MemberOverload("list_reverse", []*cel.Type{listType}, listType, - cel.FunctionBinding(func(args ...ref.Val) ref.Val { - list := args[0].(traits.Lister) - result, err := reverseList(list) + cel.UnaryBinding(func(list ref.Val) ref.Val { + result, err := reverseList(list.(traits.Lister)) if err != nil { return types.WrapErr(err) } @@ -340,17 +360,101 @@ func (lib listsLib) CompileOptions() []cel.EnvOption { ), )) } + if lib.version >= 3 { + estimators := []checker.CostOption{ + checker.OverloadCostEstimate("list_slice", estimateListSlice), + checker.OverloadCostEstimate("lists_range", estimateListsRange), + checker.OverloadCostEstimate("list_reverse", estimateListReverse), + } + if lib.version == 3 { + estimators = append(estimators, + 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...)) + } return opts } // ProgramOptions implements the Library interface method. -func (listsLib) ProgramOptions() []cel.ProgramOption { - return []cel.ProgramOption{} +func (lib *listsLib) ProgramOptions() []cel.ProgramOption { + var opts []cel.ProgramOption + if lib.version >= 3 { + trackers := []interpreter.CostTrackerOption{ + interpreter.OverloadCostTracker("list_slice", trackListOutputSize), + 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( + fmt.Sprintf("list_%s_sort", t.TypeName()), + trackListSort, + ), + interpreter.OverloadCostTracker( + fmt.Sprintf("list_%s_sortByAssociatedKeys", t.TypeName()), + trackListSortBy, + ), + ) + } + opts = append(opts, cel.CostTrackerOptions(trackers...)) + } + 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) } @@ -451,20 +555,24 @@ func sortListByAssociatedKeys(list, keys traits.Lister) (ref.Val, error) { sortedIndices := make([]ref.Val, 0, listLength) for i := types.IntZero; i < listLength; i++ { - if keys.Get(i).Type() != elem.Type() { - return nil, fmt.Errorf("list elements must have the same type") - } sortedIndices = append(sortedIndices, i) } + var err error sort.Slice(sortedIndices, func(i, j int) bool { iKey := keys.Get(sortedIndices[i]) jKey := keys.Get(sortedIndices[j]) + if iKey.Type() != elem.Type() || jKey.Type() != elem.Type() { + err = fmt.Errorf("list elements must have the same type") + return false + } return iKey.(traits.Comparer).Compare(jKey) == types.IntNegOne }) + if err != nil { + return nil, err + } sorted := make([]ref.Val, 0, listLength) - for _, sortedIdx := range sortedIndices { sorted = append(sorted, list.Get(sortedIdx)) } @@ -551,3 +659,345 @@ func templatedOverloads(types []*cel.Type, template func(t *cel.Type) cel.Functi } return overloads } + +// estimateListSlice computes an O(n) slice operation with a cost factor of 1. +func estimateListSlice(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if target == nil || len(args) != 2 { + return nil + } + sz := estimateSize(estimator, *target) + start := nodeAsUintValue(args[0], 0) + end := nodeAsUintValue(args[1], sz.Max) + return estimateAllocatingListCall(1, checker.FixedSizeEstimate(end-start)) +} + +// estimateListsRange computes an O(n) range operation with a cost factor of 1. +func estimateListsRange(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if target != nil || len(args) != 1 { + return nil + } + return estimateAllocatingListCall(1, checker.FixedSizeEstimate(nodeAsUintValue(args[0], math.MaxUint))) +} + +// estimateListReverse computes an O(n) reverse operation with a cost factor of 1. +func estimateListReverse(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { + if target == nil || len(args) != 0 { + return nil + } + return estimateAllocatingListCall(1, estimateSize(estimator, *target)) +} + +// 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 = 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 { + if target == nil || len(args) != 0 { + return nil + } + sz := estimateSize(estimator, *target) + 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 +// operations against the elements in the list against themselves which occur during the sort computation. +func estimateListSort(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 estimateListSortCost(estimator, *target, t) + } +} + +// estimateListSortBy computes an O(n^2) sort operation with a cost factor of 2 for the equality +// operations against the sort index list which occur during the sort computation. +func estimateListSortBy(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 + } + // 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) + 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 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) +} + +// 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)) + } + cost = cost.Add(callCostEstimate) + return &checker.CallEstimate{CostEstimate: cost, ResultSize: &resultSize} +} + +// trackListOutputSize computes cost as a function of the size of the result list. +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. +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)) + } + inputSize := actualSize(args[0]) + return trackAllocatingListCall(depth, inputSize) +} + +// trackListDistinct computes costs as a worst-case O(n^2) operation over the input list. +func trackListDistinct(args []ref.Val, _ ref.Val) *uint64 { + return trackListSelfCompare(args[0].(traits.Lister)) +} + +// trackListSort computes costs as a worst-case O(n^2) operation over the input list. +func trackListSort(args []ref.Val, result ref.Val) *uint64 { + return trackListSelfCompare(args[0].(traits.Lister)) +} + +// trackListSortBy computes costs as a worst-case O(n^2) operation over the sort index list. +func trackListSortBy(args []ref.Val, result ref.Val) *uint64 { + return trackListSelfCompare(args[1].(traits.Lister)) +} + +// trackListSelfCompare computes costs as a worst-case O(n^2) operation over the input list. +func trackListSelfCompare(l traits.Lister) *uint64 { + sz := actualSize(l) + costFactor := 2.0 + if sz == 0 { + return trackAllocatingListCall(costFactor, 0) + } + elem := l.Get(types.IntZero) + if elem.Type() == types.StringType || elem.Type() == types.BytesType { + costFactor += common.StringTraversalCostFactor + } + 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 { + if costFactor < 0.0 { + costFactor = 1.0 + } + cost := safeAdd(uint64(float64(size)*costFactor), callCost, common.ListCreateBaseCost) + return &cost +} + +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 + } + 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, + } + 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) + } +} diff --git a/vendor/github.com/google/cel-go/ext/math.go b/vendor/github.com/google/cel-go/ext/math.go index 250246db15bc0..e67b205defab7 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 @@ -325,6 +327,23 @@ import ( // // math.isFinite(0.0/0.0) // returns false // math.isFinite(1.2) // returns true +// +// # Math.Sqrt +// +// Introduced at version: 2 +// +// Returns the square root of the given input as double +// Throws error for negative or non-numeric inputs +// +// math.sqrt() -> +// math.sqrt() -> +// math.sqrt() -> +// +// Examples: +// +// 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 { @@ -357,6 +376,9 @@ const ( absFunc = "math.abs" signFunc = "math.sign" + // SquareRoot function + sqrtFunc = "math.sqrt" + // Bitwise functions bitAndFunc = "math.bitAnd" bitOrFunc = "math.bitOr" @@ -548,12 +570,47 @@ func (lib *mathLib) CompileOptions() []cel.EnvOption { ), ) } + if lib.version >= 2 { + opts = append(opts, + cel.Function(sqrtFunc, + cel.Overload("math_sqrt_double", []*cel.Type{cel.DoubleType}, cel.DoubleType, + cel.UnaryBinding(sqrt)), + cel.Overload("math_sqrt_int", []*cel.Type{cel.IntType}, cel.DoubleType, + cel.UnaryBinding(sqrt)), + cel.Overload("math_sqrt_uint", []*cel.Type{cel.UintType}, cel.DoubleType, + cel.UnaryBinding(sqrt)), + ), + ) + } + 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) { @@ -691,6 +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))) + case types.Int: + return types.Double(math.Sqrt(float64(v))) + case types.Uint: + return types.Double(math.Sqrt(float64(v))) + default: + return types.NewErr("no such overload: sqrt") + } +} + func bitAndPairInt(first, second ref.Val) ref.Val { l := first.(types.Int) r := second.(types.Int) @@ -899,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 1c33def493568..d9f5fab0decc7 100644 --- a/vendor/github.com/google/cel-go/ext/native.go +++ b/vendor/github.com/google/cel-go/ext/native.go @@ -81,7 +81,7 @@ var ( // the time that it is invoked. // // There is also the possibility to rename the fields of native structs by setting the `cel` tag -// for fields you want to override. In order to enable this feature, pass in the `EnableStructTag` +// for fields you want to override. In order to enable this feature, pass in the `ParseStructTags(true)` // option. Here is an example to see it in action: // // ```go @@ -154,7 +154,7 @@ func fieldNameByTag(structTagToParse string) func(field reflect.StructField) str // 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 + // https://pkg.go.dev/go.yaml.in/yaml/v3#Marshal name := splits[0] return name } @@ -164,6 +164,10 @@ func fieldNameByTag(structTagToParse string) func(field reflect.StructField) str } } +func isSkippedFieldName(name string) bool { + return name == "" || 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, @@ -286,9 +290,13 @@ func toFieldName(fieldNameHandler NativeTypesFieldNameHandler, f reflect.StructF func (tp *nativeTypeProvider) FindStructFieldNames(typeName string) ([]string, bool) { if t, found := tp.nativeTypes[typeName]; found { fieldCount := t.refType.NumField() - fields := make([]string, fieldCount) + fields := make([]string, 0, fieldCount) for i := 0; i < fieldCount; i++ { - fields[i] = toFieldName(tp.options.fieldNameHandler, t.refType.Field(i)) + fieldName := toFieldName(tp.options.fieldNameHandler, t.refType.Field(i)) + if isSkippedFieldName(fieldName) { + continue + } + fields = append(fields, fieldName) } return fields, true } @@ -434,10 +442,18 @@ func convertToCelType(refType reflect.Type) (*cel.Type, bool) { if refType == timestampType { return cel.TimestampType, true } + if refType.Implements(refValType) { + emptyCelVal := reflect.New(refType).Elem().Interface().(ref.Val) + return emptyCelVal.Type().(*cel.Type), true + } return cel.ObjectType( 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().(*cel.Type), true + } if refType.Implements(pbMsgInterfaceType) { pbMsg := reflect.New(refType.Elem()).Interface().(protoreflect.ProtoMessage) return cel.ObjectType(string(pbMsg.ProtoReflect().Descriptor().FullName())), true @@ -501,6 +517,9 @@ func (o *nativeObj) ConvertToNative(typeDesc reflect.Type) (any, error) { continue } fieldName := toFieldName(o.valType.fieldNameHandler, fieldType) + if isSkippedFieldName(fieldName) { + continue + } fieldCELVal := o.NativeToValue(fieldValue.Interface()) fieldJSONVal, err := fieldCELVal.ConvertToNative(jsonValueType) if err != nil { @@ -608,8 +627,13 @@ func newNativeTypes(fieldNameHandler NativeTypesFieldNameHandler, rawType reflec alreadySeen := make(map[string]struct{}) var iterateStructMembers func(reflect.Type) iterateStructMembers = func(t reflect.Type) { + if t.Implements(reflect.TypeFor[ref.Val]()) { + // skip this field since it's a CEL ref.Val instance. + return + } if k := t.Kind(); k == reflect.Pointer || k == reflect.Slice || k == reflect.Array || k == reflect.Map { - t = t.Elem() + iterateStructMembers(t.Elem()) + return } if t.Kind() != reflect.Struct { return @@ -654,7 +678,9 @@ func newNativeType(fieldNameHandler NativeTypesFieldNameHandler, rawType reflect for idx := 0; idx < refType.NumField(); idx++ { field := refType.Field(idx) fieldName := toFieldName(fieldNameHandler, field) - + if isSkippedFieldName(fieldName) { + continue + } if _, found := fieldNames[fieldName]; found { return nil, fmt.Errorf("invalid field name `%s` in struct `%s`: %w", fieldName, refType.Name(), errDuplicatedFieldName) } else { @@ -724,6 +750,10 @@ func (t *nativeType) Value() any { // 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 isSkippedFieldName(fieldName) { + return reflect.StructField{}, false + } + if t.fieldNameHandler == nil { return t.refType.FieldByName(fieldName) } @@ -790,7 +820,8 @@ func isSupportedType(refType reflect.Type) bool { } var ( - pbMsgInterfaceType = reflect.TypeOf((*protoreflect.ProtoMessage)(nil)).Elem() - timestampType = reflect.TypeOf(time.Now()) - durationType = reflect.TypeOf(time.Nanosecond) + pbMsgInterfaceType = reflect.TypeFor[protoreflect.ProtoMessage]() + refValType = reflect.TypeFor[ref.Val]() + timestampType = reflect.TypeFor[time.Time]() + durationType = reflect.TypeFor[time.Duration]() ) 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 0000000000000..bca065707753f --- /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 new file mode 100644 index 0000000000000..bd222f170eeaf --- /dev/null +++ b/vendor/github.com/google/cel-go/ext/regex.go @@ -0,0 +1,452 @@ +// 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 ( + "errors" + "fmt" + "math" + "regexp" + "strconv" + "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 ( + regexReplace = "regex.replace" + regexExtract = "regex.extract" + regexExtractAll = "regex.extractAll" +) + +// Regex returns a cel.EnvOption to configure extended functions for regular +// expression operations. +// +// Note: 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. +// +// This library depends on the CEL optional type. Please ensure that the +// cel.OptionalTypes() is enabled when using regex extensions. +// +// # 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', r'\2 \1') == 'ba fo' +// regex.replace('test', '(.)', r'\2') \\ Runtime Error invalid replace string +// regex.replace('foo bar', '(', '$2 $1') \\ Runtime Error invalid regex string +// regex.replace('id=123', r'id=(?P\d+)', r'value: \values') \\ Runtime Error invalid replace string +// +// # Extract +// +// The `regex.extract` function returns the first match of a regex pattern in a +// 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 capture 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 +func Regex(options ...RegexOptions) cel.EnvOption { + s := ®exLib{ + version: math.MaxUint32, + } + for _, o := range options { + s = o(s) + } + return cel.Lib(s) +} + +// RegexOptions declares a functional operator for configuring regex extension. +type RegexOptions func(*regexLib) *regexLib + +// RegexVersion configures the version of the Regex library definitions to use. See [Regex] for supported values. +func RegexVersion(version uint32) RegexOptions { + return func(lib *regexLib) *regexLib { + lib.version = version + return lib + } +} + +type regexLib struct { + version uint32 +} + +// LibraryName implements that SingletonLibrary interface method. +func (r *regexLib) LibraryName() string { + return "cel.lib.ext.regex" +} + +// CompileOptions implements the cel.Library interface method. +func (r *regexLib) CompileOptions() []cel.EnvOption { + optionalTypesEnabled := func(env *cel.Env) (*cel.Env, error) { + if !env.HasLibrary("cel.lib.optional") { + return nil, errors.New("regex library requires the optional library") + } + return env, nil + } + opts := []cel.EnvOption{ + cel.Function(regexExtract, + cel.Overload("regex_extract_string_string", []*cel.Type{cel.StringType, cel.StringType}, cel.OptionalType(cel.StringType), + cel.BinaryBinding(extract))), + + cel.Function(regexExtractAll, + cel.Overload("regex_extractAll_string_string", []*cel.Type{cel.StringType, cel.StringType}, cel.ListType(cel.StringType), + cel.BinaryBinding(extractAll))), + + cel.Function(regexReplace, + cel.Overload("regex_replace_string_string_string", []*cel.Type{cel.StringType, cel.StringType, cel.StringType}, cel.StringType, + cel.FunctionBinding(regReplace)), + 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 +} + +// ProgramOptions implements the cel.Library interface method +func (r *regexLib) ProgramOptions() []cel.ProgramOption { + 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()), + ), + } +} + +func regReplace(args ...ref.Val) ref.Val { + target := args[0].(types.String) + regexStr := args[1].(types.String) + replaceStr := args[2].(types.String) + + return regReplaceN(target, regexStr, replaceStr, types.Int(-1)) +} + +func regReplaceN(args ...ref.Val) ref.Val { + target := string(args[0].(types.String)) + regexStr := string(args[1].(types.String)) + replaceStr := string(args[2].(types.String)) + replaceCount := int64(args[3].(types.Int)) + + if replaceCount == 0 { + return types.String(target) + } + + // If replaceCount is negative, just do a replaceAll. + if replaceCount < 0 { + replaceCount = -1 + } + + re, err := regexp.Compile(regexStr) + if err != nil { + return types.WrapErr(err) + } + + var resultBuilder strings.Builder + var lastIndex int + counter := int64(0) + + matches := re.FindAllStringSubmatchIndex(target, -1) + + for _, match := range matches { + if replaceCount != -1 && counter >= replaceCount { + break + } + + processedReplacement, err := replaceStrValidator(target, re, match, replaceStr) + if err != nil { + return types.WrapErr(err) + } + + resultBuilder.WriteString(target[lastIndex:match[0]]) + resultBuilder.WriteString(processedReplacement) + lastIndex = match[1] + counter++ + } + + resultBuilder.WriteString(target[lastIndex:]) + return types.String(resultBuilder.String()) +} + +func replaceStrValidator(target string, re *regexp.Regexp, match []int, replacement string) (string, error) { + groupCount := re.NumSubexp() + var sb strings.Builder + runes := []rune(replacement) + + for i := 0; i < len(runes); i++ { + c := runes[i] + + if c != '\\' { + sb.WriteRune(c) + continue + } + + if i+1 >= len(runes) { + return "", fmt.Errorf("invalid replacement string: '%s' \\ not allowed at end", replacement) + } + + i++ + nextChar := runes[i] + + if nextChar == '\\' { + sb.WriteRune('\\') + continue + } + + groupNum, err := strconv.Atoi(string(nextChar)) + if err != nil { + return "", fmt.Errorf("invalid replacement string: '%s' \\ must be followed by a digit or \\", replacement) + } + + if groupNum > groupCount { + return "", fmt.Errorf("replacement string references group %d but regex has only %d group(s)", groupNum, groupCount) + } + + if match[2*groupNum] != -1 { + sb.WriteString(target[match[2*groupNum]:match[2*groupNum+1]]) + } + } + return sb.String(), nil +} + +func extract(target, regexStr ref.Val) ref.Val { + t := string(target.(types.String)) + r := string(regexStr.(types.String)) + re, err := regexp.Compile(r) + if err != nil { + return types.WrapErr(err) + } + + if len(re.SubexpNames())-1 > 1 { + return types.WrapErr(fmt.Errorf("regular expression has more than one capturing group: %q", r)) + } + + matches := re.FindStringSubmatch(t) + if len(matches) == 0 { + return types.OptionalNone + } + + // If there is a capturing group, return the first match; otherwise, return the whole match. + if len(matches) > 1 { + capturedGroup := matches[1] + // If optional group is empty, return OptionalNone. + if capturedGroup == "" { + return types.OptionalNone + } + return types.OptionalOf(types.String(capturedGroup)) + } + return types.OptionalOf(types.String(matches[0])) +} + +func extractAll(target, regexStr ref.Val) ref.Val { + t := string(target.(types.String)) + r := string(regexStr.(types.String)) + re, err := regexp.Compile(r) + if err != nil { + return types.WrapErr(err) + } + + groupCount := len(re.SubexpNames()) - 1 + if groupCount > 1 { + return types.WrapErr(fmt.Errorf("regular expression has more than one capturing group: %q", r)) + } + + matches := re.FindAllStringSubmatch(t, -1) + result := make([]string, 0, len(matches)) + if len(matches) == 0 { + return types.NewStringList(types.DefaultTypeAdapter, result) + } + + if groupCount != 1 { + for _, match := range matches { + result = append(result, match[0]) + } + return types.NewStringList(types.DefaultTypeAdapter, result) + } + + for _, match := range matches { + if match[1] != "" { + result = append(result, match[1]) + } + } + 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 9a9ef6eef6d81..63c019ad9f7d8 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" @@ -236,43 +234,21 @@ func setsEquivalent(listA, listB ref.Val) ref.Val { func estimateSetsCost(costFactor float64) checker.FunctionEstimator { return func(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { - if len(args) == 2 { - arg0Size := estimateSize(estimator, args[0]) - arg1Size := estimateSize(estimator, args[1]) - costEstimate := arg0Size.Multiply(arg1Size).MultiplyByCostFactor(costFactor).Add(callCostEstimate) - return &checker.CallEstimate{CostEstimate: costEstimate} + if len(args) != 2 { + return nil } - return nil - } -} - -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 + arg0Size := estimateSize(estimator, args[0]) + arg1Size := estimateSize(estimator, args[1]) + costEstimate := arg0Size.Multiply(arg1Size).MultiplyByCostFactor(costFactor).Add(callCostEstimate) + 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.CostEstimate{Min: 1, Max: 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 2e590a4c57b3c..1f7732f2fb4c5 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. @@ -286,10 +309,15 @@ const ( // // 'gums'.reverse() // returns 'smug' // 'John Smith'.reverse() // returns 'htimS nhoJ' +// +// 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. +// +// .format() -> func Strings(options ...StringsOption) cel.EnvOption { s := &stringLib{ - version: math.MaxUint32, - validateFormat: true, + version: math.MaxUint32, } for _, o := range options { s = o(s) @@ -298,9 +326,9 @@ func Strings(options ...StringsOption) cel.EnvOption { } type stringLib struct { - locale string - version uint32 - validateFormat bool + locale string + version uint32 + maxPrecision int } // LibraryName implements the SingletonLibrary interface method. @@ -314,6 +342,8 @@ type StringsOption func(*stringLib) *stringLib // StringsLocale configures the library with the given locale. The locale tag will // be checked for validity at the time that EnvOptions are configured. If this option // is not passed, string.format will behave as if en_US was passed as the locale. +// +// If StringsVersion is greater than or equal to 4, this option is ignored. func StringsLocale(locale string) StringsOption { return func(sl *stringLib) *stringLib { sl.locale = locale @@ -340,18 +370,27 @@ func StringsVersion(version uint32) StringsOption { // StringsValidateFormatCalls validates type-checked ASTs to ensure that string.format() calls have // valid formatting clauses and valid argument types for each clause. // -// Enabled by default. +// Deprecated func StringsValidateFormatCalls(value bool) StringsOption { return func(s *stringLib) *stringLib { - s.validateFormat = value return s } } +// 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" - if lib.locale != "" { + if lib.version < 4 && lib.locale != "" { // ensure locale is properly-formed if set _, err := language.Parse(lib.locale) if err != nil { @@ -465,22 +504,37 @@ 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 { - opts = append(opts, cel.Function("format", - cel.MemberOverload("string_format", []*cel.Type{cel.StringType, cel.ListType(cel.DynType)}, cel.StringType, - 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)) - }))), + if lib.version >= 4 { + opts = append(opts, cel.Function("format", + cel.MemberOverload("string_format", []*cel.Type{cel.StringType, cel.ListType(cel.DynType)}, cel.StringType, + 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}, maxPrecision)) + })))) + } else { + opts = append(opts, cel.Function("format", + cel.MemberOverload("string_format", []*cel.Type{cel.StringType, cel.ListType(cel.DynType)}, cel.StringType, + 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, maxPrecision)) + })))) + } + opts = append(opts, cel.Function("strings.quote", cel.Overload("strings_quote", []*cel.Type{cel.StringType}, cel.StringType, cel.UnaryBinding(func(str ref.Val) ref.Val { s := str.(types.String) return stringOrError(quote(string(s))) - }))), - - cel.ASTValidators(stringFormatValidator{})) - + })))) } if lib.version >= 2 { opts = append(opts, @@ -529,14 +583,66 @@ func (lib *stringLib) CompileOptions() []cel.EnvOption { }))), ) } - if lib.validateFormat { - opts = append(opts, cel.ASTValidators(stringFormatValidator{})) + if lib.version >= 1 { + if lib.version >= 4 { + opts = append(opts, cel.ASTValidators(stringFormatValidatorV2{maxPrecision: maxPrecision})) + } else { + 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{} } @@ -557,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 @@ -590,19 +700,27 @@ func lastIndexOf(str, substr string) (int64, error) { if substr == "" { return int64(len(runes)), nil } + + if len(str) < len(substr) { + return -1, nil + } return lastIndexOfOffset(str, substr, int64(len(runes)-1)) } 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 @@ -771,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 220e23d47524c..40ac2ba6962dc 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 c20d19de1b7af..bc9296ed4e908 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 @@ -158,7 +190,8 @@ type PartialActivation interface { // partialActivationConverter indicates whether an Activation implementation supports conversion to a PartialActivation type partialActivationConverter interface { - asPartialActivation() (PartialActivation, bool) + // AsPartialActivation converts the current activation to a PartialActivation + AsPartialActivation() (PartialActivation, bool) } // partActivation is the default implementations of the PartialActivation interface. @@ -172,19 +205,20 @@ func (a *partActivation) UnknownAttributePatterns() []*AttributePattern { return a.unknowns } -// asPartialActivation returns the partActivation as a PartialActivation interface. -func (a *partActivation) asPartialActivation() (PartialActivation, bool) { +// AsPartialActivation returns the partActivation as a PartialActivation interface. +func (a *partActivation) AsPartialActivation() (PartialActivation, bool) { return a, true } -func asPartialActivation(vars Activation) (PartialActivation, bool) { +// AsPartialActivation walks the activation hierarchy and returns the first PartialActivation, if found. +func AsPartialActivation(vars Activation) (PartialActivation, bool) { // Only internal activation instances may implement this interface if pv, ok := vars.(partialActivationConverter); ok { - return pv.asPartialActivation() + return pv.AsPartialActivation() } // Since Activations may be hierarchical, test whether a parent converts to a PartialActivation if vars.Parent() != nil { - return asPartialActivation(vars.Parent()) + return AsPartialActivation(vars.Parent()) } return nil, false } 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 0000000000000..4e391196bf5d6 --- /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 7e5c2db0fc694..bbaca52269aa6 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 { @@ -358,7 +371,7 @@ func (m *attributeMatcher) AddQualifier(qual Qualifier) (Attribute, error) { func (m *attributeMatcher) Resolve(vars Activation) (any, error) { id := m.NamespacedAttribute.ID() // Bug in how partial activation is resolved, should search parents as well. - partial, isPartial := asPartialActivation(vars) + partial, isPartial := AsPartialActivation(vars) if isPartial { unk, err := m.fac.matchesUnknownPatterns( partial, diff --git a/vendor/github.com/google/cel-go/interpreter/attributes.go b/vendor/github.com/google/cel-go/interpreter/attributes.go index b1b3aacc834fe..26d8eb0f3e487 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 502db35fc0f5b..9c973664ac035 100644 --- a/vendor/github.com/google/cel-go/interpreter/decorators.go +++ b/vendor/github.com/google/cel-go/interpreter/decorators.go @@ -25,9 +25,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 +53,8 @@ func decObserveEval(observer EvalObserver) InterpretableDecorator { }, nil default: return &evalWatch{ - Interpretable: i, - observer: observer, + InterpretableV2: i, + observer: observer, }, nil } } @@ -58,8 +62,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 +74,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 +108,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 +128,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 +138,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 +169,7 @@ func decRegexOptimizer(regexOptimizations ...*RegexOptimization) InterpretableDe } } -func maybeOptimizeConstUnary(i Interpretable, call InterpretableCall) (Interpretable, error) { +func maybeOptimizeConstUnary(i InterpretableV2, call InterpretableCall) (InterpretableV2, error) { args := call.Args() if len(args) != 1 { return i, nil @@ -181,7 +185,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 +195,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 +213,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 0000000000000..20ab313c8f7a3 --- /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 591b7688b7519..906c4f805fbd9 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,21 +127,107 @@ 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 } +// ObservableInterpretable is an Interpretable which supports stateful observation, such as tracing +// or cost-tracking. +type ObservableInterpretable struct { + InterpretableV2 + observers []StatefulObserver +} + +// ID implements the Interpretable method to get the expression id associated with the step. +func (oi *ObservableInterpretable) ID() int64 { + 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.ObserveExec(AsFrame(vars), func(any) {}) +} + +// ObserveEval 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) ObserveEval(vars Activation, observer func(any)) ref.Val { + 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 { + 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(state) + } + 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(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 { @@ -121,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)) @@ -134,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) @@ -153,12 +277,9 @@ 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 } - if opt, isOpt := out.(types.Optional); isOpt { - return opt.HasValue(), nil - } return present, nil } @@ -192,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 @@ -204,7 +330,7 @@ func (cons *evalConst) Value() ref.Val { type evalOr struct { id int64 - terms []Interpretable + terms []InterpretableV2 } // ID implements the Interpretable interface method. @@ -212,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 { @@ -245,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. @@ -255,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 { @@ -288,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. @@ -299,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 @@ -323,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. @@ -338,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 @@ -362,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 { @@ -378,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. @@ -394,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 @@ -413,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) { @@ -434,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 @@ -445,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 @@ -465,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 @@ -492,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 @@ -503,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, @@ -533,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] @@ -558,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 @@ -569,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 @@ -586,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) +} + +// Eval implements the Interpretable interface method. +func (l *evalList) Eval(ctx Activation) ref.Val { + return l.Exec(AsFrame(ctx)) } -func (l *evalList) InitVals() []Interpretable { +func (l *evalList) InitVals() []InterpretableV2 { return l.elems } @@ -620,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 @@ -632,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)) @@ -658,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) } -func (m *evalMap) InitVals() []Interpretable { +// Eval implements the Interpretable interface method. +func (m *evalMap) Eval(ctx Activation) ref.Val { + return m.Exec(AsFrame(ctx)) +} + +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] @@ -685,7 +899,7 @@ type evalObj struct { id int64 typeName string fields []string - vals []Interpretable + vals []InterpretableV2 optionals []bool hasOptionals bool provider types.Provider @@ -696,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)) @@ -718,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 } @@ -736,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 @@ -755,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 } @@ -786,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 } @@ -802,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 } @@ -814,20 +1042,30 @@ 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 } -// Eval implements the Interpretable interface method. -func (e *evalWatch) Eval(ctx Activation) ref.Val { - val := e.Interpretable.Eval(ctx) - e.observer(e.ID(), e.Interpretable, val) +// 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 { + return e.Exec(AsFrame(vars)) +} + // evalWatchAttr describes a watcher of an InterpretableAttribute Interpretable. // // Since the watcher may be selected against at a later stage in program planning, the watcher @@ -880,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(e.ID(), e.InterpretableAttribute, val) - return val + return e.Exec(AsFrame(vars)) } // evalWatchConstQual observes the qualification of an object using a constant boolean, int, @@ -904,7 +1147,7 @@ func (e *evalWatchConstQual) Qualify(vars Activation, obj any) (any, error) { } else { val = e.adapter.NativeToValue(out) } - e.observer(e.ID(), e.ConstantQualifier, val) + e.observer(vars, e.ID(), e.ConstantQualifier, val) return out, err } @@ -920,7 +1163,7 @@ func (e *evalWatchConstQual) QualifyIfPresent(vars Activation, obj any, presence val = types.Bool(present) } if present || presenceOnly { - e.observer(e.ID(), e.ConstantQualifier, val) + e.observer(vars, e.ID(), e.ConstantQualifier, val) } return out, present, err } @@ -947,7 +1190,7 @@ func (e *evalWatchAttrQual) Qualify(vars Activation, obj any) (any, error) { } else { val = e.adapter.NativeToValue(out) } - e.observer(e.ID(), e.Attribute, val) + e.observer(vars, e.ID(), e.Attribute, val) return out, err } @@ -963,7 +1206,7 @@ func (e *evalWatchAttrQual) QualifyIfPresent(vars Activation, obj any, presenceO val = types.Bool(present) } if present || presenceOnly { - e.observer(e.ID(), e.Attribute, val) + e.observer(vars, e.ID(), e.Attribute, val) } return out, present, err } @@ -984,7 +1227,7 @@ func (e *evalWatchQual) Qualify(vars Activation, obj any) (any, error) { } else { val = e.adapter.NativeToValue(out) } - e.observer(e.ID(), e.Qualifier, val) + e.observer(vars, e.ID(), e.Qualifier, val) return out, err } @@ -1000,7 +1243,7 @@ func (e *evalWatchQual) QualifyIfPresent(vars Activation, obj any, presenceOnly val = types.Bool(present) } if present || presenceOnly { - e.observer(e.ID(), e.Qualifier, val) + e.observer(vars, e.ID(), e.Qualifier, val) } return out, present, err } @@ -1011,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(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. @@ -1029,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 { @@ -1065,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. @@ -1076,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 { @@ -1112,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 { @@ -1125,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") @@ -1146,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 @@ -1177,23 +1440,28 @@ 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(ctx Activation, obj any) (any, error) { - return a.attr.Qualify(ctx, obj) +func (a *evalAttr) Qualify(vars Activation, obj any) (any, error) { + return a.attr.Qualify(vars, obj) } // QualifyIfPresent proxies to the Attribute's QualifyIfPresent method. -func (a *evalAttr) QualifyIfPresent(ctx Activation, obj any, presenceOnly bool) (any, bool, error) { - return a.attr.QualifyIfPresent(ctx, obj, presenceOnly) +func (a *evalAttr) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) { + return a.attr.QualifyIfPresent(vars, obj, presenceOnly) } func (a *evalAttr) IsOptional() bool { @@ -1211,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() } @@ -1225,13 +1493,18 @@ func (c *evalWatchConstructor) ID() int64 { return c.constructor.ID() } -// Eval implements the Interpretable Eval function. -func (c *evalWatchConstructor) Eval(ctx Activation) ref.Val { - val := c.constructor.Eval(ctx) - c.observer(c.ID(), c.constructor, val) +// 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 { + return c.Exec(AsFrame(vars)) +} + func invalidOptionalEntryInit(field any, value ref.Val) ref.Val { return types.NewErr("cannot initialize optional entry '%v' from non-optional value %v", field, value) } @@ -1241,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 } @@ -1265,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 @@ -1284,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() } @@ -1310,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 } @@ -1333,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) @@ -1358,28 +1631,53 @@ 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 partial, isPartial := pv.asPartialActivation(); isPartial { + if pv, ok := f.frame.parent.Activation.(partialActivationConverter); ok { + if partial, isPartial := pv.AsPartialActivation(); isPartial { return partial.UnknownAttributePatterns() } } return []*AttributePattern{} } -func (f *folder) asPartialActivation() (PartialActivation, bool) { - if pv, ok := f.activation.(partialActivationConverter); ok { - if _, isPartial := pv.asPartialActivation(); isPartial { +func (f *folder) AsPartialActivation() (PartialActivation, bool) { + if pv, ok := f.frame.parent.Activation.(partialActivationConverter); ok { + if _, isPartial := pv.AsPartialActivation(); isPartial { return f, true } } @@ -1390,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 { @@ -1409,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 @@ -1420,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 0aca74d88b9f6..ef13ab9226381 100644 --- a/vendor/github.com/google/cel-go/interpreter/interpreter.go +++ b/vendor/github.com/google/cel-go/interpreter/interpreter.go @@ -18,36 +18,41 @@ package interpreter import ( + "errors" + "github.com/google/cel-go/common/ast" "github.com/google/cel-go/common/containers" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" ) -// Interpreter generates a new Interpretable from a checked or unchecked expression. +// PlannerOption configures the program plan options during interpretable setup. +type PlannerOption func(*planner) (*planner, error) + +// Interpreter generates a new InterpretableV2 from a checked or unchecked expression. type Interpreter interface { - // NewInterpretable creates an Interpretable from a checked expression and an - // optional list of InterpretableDecorator values. - NewInterpretable(exprAST *ast.AST, decorators ...InterpretableDecorator) (Interpretable, error) + // NewInterpretable creates an InterpretableV2 from a checked expression and an + // optional list of PlannerOption values. + NewInterpretable(exprAST *ast.AST, opts ...PlannerOption) (InterpretableV2, error) } // EvalObserver is a functional interface that accepts an expression id and an observed value. // The id identifies the expression that was evaluated, the programStep is the Interpretable or Qualifier that // was evaluated and value is the result of the evaluation. -type EvalObserver func(id int64, programStep any, value ref.Val) +type EvalObserver func(vars Activation, id int64, programStep any, value ref.Val) -// Observe constructs a decorator that calls all the provided observers in order after evaluating each Interpretable -// or Qualifier during program evaluation. -func Observe(observers ...EvalObserver) InterpretableDecorator { - if len(observers) == 1 { - return decObserveEval(observers[0]) - } - observeFn := func(id int64, programStep any, val ref.Val) { - for _, observer := range observers { - observer(id, programStep, val) - } - } - return decObserveEval(observeFn) +// StatefulObserver observes evaluation while tracking or utilizing stateful behavior. +type StatefulObserver interface { + // InitState configures stateful metadata on the execution frame. + InitState(*ExecutionFrame) (any, error) + + // 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(AsFrame(activation)) + // to find the metadata that needs to be updated upon invocation. + Observe(Activation, int64, any, ref.Val) } // EvalCancelledError represents a cancelled program evaluation operation. @@ -73,24 +78,103 @@ const ( CostLimitExceeded ) -// TODO: Replace all usages of TrackState with EvalStateObserver +// evalStateOption configures the evalStateFactory behavior. +type evalStateOption func(*evalStateFactory) *evalStateFactory + +// EvalStateFactory configures the EvalState generator to be used by the EvalStateObserver. +func EvalStateFactory(factory func() EvalState) evalStateOption { + return func(fac *evalStateFactory) *evalStateFactory { + fac.factory = factory + return fac + } +} + +// EvalStateObserver provides an observer which records the value associated with the given expression id. +// EvalState must be provided to the observer. +func EvalStateObserver(opts ...evalStateOption) PlannerOption { + et := &evalStateFactory{factory: NewEvalState} + for _, o := range opts { + et = o(et) + } + return func(p *planner) (*planner, error) { + if et.factory == nil { + return nil, errors.New("eval state factory not configured") + } + p.observers = append(p.observers, et) + p.decorators = append(p.decorators, decObserveEval(et.Observe)) + return p, nil + } +} + +// 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 +} + +// 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. +type evalStateFactory struct { + factory func() EvalState +} + +// 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(frame *ExecutionFrame) (any, error) { + if frame.ctx != nil && frame.ctx.state != nil { + return frame.ctx.state, nil + } + state := et.factory() + 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(frame *ExecutionFrame) any { + if frame.ctx == nil { + return nil + } + return frame.ctx.state +} -// TrackState decorates each expression node with an observer which records the value -// associated with the given expression id. EvalState must be provided to the decorator. -// This decorator is not thread-safe, and the EvalState must be reset between Eval() -// calls. -// DEPRECATED: Please use EvalStateObserver instead. It composes gracefully with additional observers. -func TrackState(state EvalState) InterpretableDecorator { - return Observe(EvalStateObserver(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) { + frame := AsFrame(vars) + if frame.ctx == nil || frame.ctx.state == nil { + return + } + frame.ctx.state.SetValue(id, val) } -// EvalStateObserver provides an observer which records the value -// associated with the given expression id. EvalState must be provided to the observer. -// This decorator is not thread-safe, and the EvalState must be reset between Eval() -// calls. -func EvalStateObserver(state EvalState) EvalObserver { - return func(id int64, programStep any, val ref.Val) { - 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 } } @@ -99,11 +183,8 @@ func EvalStateObserver(state EvalState) EvalObserver { // insight into the evaluation state of the entire expression. EvalState must be // provided to the decorator. This decorator is not thread-safe, and the EvalState // must be reset between Eval() calls. -func ExhaustiveEval() InterpretableDecorator { - ex := decDisableShortcircuits() - return func(i Interpretable) (Interpretable, error) { - return ex(i) - } +func ExhaustiveEval() PlannerOption { + return CustomDecoratorV2(decDisableShortcircuits()) } // InterruptableEval annotates comprehension loops with information that indicates they @@ -111,14 +192,14 @@ func ExhaustiveEval() InterpretableDecorator { // // 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() InterpretableDecorator { - return decInterruptFolds() +func InterruptableEval() PlannerOption { + 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() InterpretableDecorator { - return decOptimize() +func Optimize() PlannerOption { + return CustomDecoratorV2(decOptimize()) } // RegexOptimization provides a way to replace an InterpretableCall for a regex function when the @@ -142,8 +223,8 @@ type RegexOptimization struct { // CompileRegexConstants compiles regex pattern string constants at program creation time and reports any regex pattern // compile errors. -func CompileRegexConstants(regexOptimizations ...*RegexOptimization) InterpretableDecorator { - return decRegexOptimizer(regexOptimizations...) +func CompileRegexConstants(regexOptimizations ...*RegexOptimization) PlannerOption { + return CustomDecoratorV2(decRegexOptimizer(regexOptimizations...)) } type exprInterpreter struct { @@ -169,17 +250,17 @@ 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, - decorators ...InterpretableDecorator) (Interpretable, error) { - p := newPlanner( - i.dispatcher, - i.provider, - i.adapter, - i.attrFactory, - i.container, - checked, - decorators...) + opts ...PlannerOption) (InterpretableV2, error) { + p := newPlanner(i.dispatcher, i.provider, i.adapter, i.attrFactory, i.container, checked) + var err error + for _, o := range opts { + p, err = o(p) + if err != nil { + return nil, err + } + } return p.Plan(checked.Expr()) } diff --git a/vendor/github.com/google/cel-go/interpreter/planner.go b/vendor/github.com/google/cel-go/interpreter/planner.go index f0fd4eaf94c73..396a9803fc6f1 100644 --- a/vendor/github.com/google/cel-go/interpreter/planner.go +++ b/vendor/github.com/google/cel-go/interpreter/planner.go @@ -25,12 +25,6 @@ import ( "github.com/google/cel-go/common/types" ) -// interpretablePlanner creates an Interpretable evaluation plan from a proto Expr value. -type interpretablePlanner interface { - // Plan generates an Interpretable value (or error) from the input proto Expr. - Plan(expr ast.Expr) (Interpretable, error) -} - // newPlanner creates an interpretablePlanner which references a Dispatcher, TypeProvider, // TypeAdapter, Container, and CheckedExpr value. These pieces of data are used to resolve // functions, types, and namespaced identifiers at plan time rather than at runtime since @@ -40,8 +34,7 @@ func newPlanner(disp Dispatcher, adapter types.Adapter, attrFactory AttributeFactory, cont *containers.Container, - exprAST *ast.AST, - decorators ...InterpretableDecorator) interpretablePlanner { + exprAST *ast.AST) *planner { return &planner{ disp: disp, provider: provider, @@ -50,7 +43,8 @@ func newPlanner(disp Dispatcher, container: cont, refMap: exprAST.ReferenceMap(), typeMap: exprAST.TypeMap(), - decorators: decorators, + decorators: make([]InterpretableDecoratorV2, 0), + observers: make([]StatefulObserver, 0), } } @@ -63,7 +57,14 @@ 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 @@ -71,7 +72,19 @@ type planner struct { // 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) { +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{InterpretableV2: i, observers: p.observers}, nil +} + +func (p *planBuilder) plan(expr ast.Expr) (InterpretableV2, error) { switch expr.Kind() { case ast.CallKind: return p.decorate(p.planCall(expr)) @@ -96,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 } @@ -110,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 @@ -152,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 { @@ -161,7 +180,7 @@ func (p *planner) planSelect(expr ast.Expr) (Interpretable, error) { sel := expr.AsSelect() // Plan the operand evaluation. - op, err := p.Plan(sel.Operand()) + op, err := p.plan(sel.Operand()) if err != nil { return nil, err } @@ -208,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()) @@ -218,16 +237,16 @@ 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) + arg, err := p.plan(target) if err != nil { return nil, err } args[0] = arg } for i, argExpr := range call.Args() { - arg, err := p.Plan(argExpr) + arg, err := p.plan(argExpr) if err != nil { return nil, err } @@ -262,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) @@ -284,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) } @@ -301,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 @@ -329,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 @@ -358,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 @@ -386,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], @@ -395,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], @@ -404,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, @@ -412,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, @@ -420,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 @@ -448,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()] @@ -483,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() @@ -494,9 +535,9 @@ 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) + elemVal, err := p.plan(elem) if err != nil { return nil, err } @@ -512,22 +553,22 @@ 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() - keyVal, err := p.Plan(entry.Key()) + keyVal, err := p.plan(entry.Key()) if err != nil { return nil, err } keys[i] = keyVal - valVal, err := p.Plan(entry.Value()) + valVal, err := p.plan(entry.Value()) if err != nil { return nil, err } @@ -546,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 { @@ -555,12 +596,12 @@ 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() fields[i] = field.Name() - val, err := p.Plan(field.Value()) + val, err := p.plan(field.Value()) if err != nil { return nil, err } @@ -580,28 +621,31 @@ 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()) + accu, err := p.plan(fold.AccuInit()) if err != nil { return nil, err } - iterRange, err := p.Plan(fold.IterRange()) + iterRange, err := p.plan(fold.IterRange()) if err != nil { return nil, err } - cond, err := p.Plan(fold.LoopCondition()) + p.pushLocalVars(fold.AccuVar(), fold.IterVar(), fold.IterVar2()) + cond, err := p.plan(fold.LoopCondition()) if err != nil { return nil, err } - step, err := p.Plan(fold.LoopStep()) + step, err := p.plan(fold.LoopStep()) if err != nil { return nil, err } - result, err := p.Plan(fold.Result()) + 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(), @@ -617,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 @@ -640,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. @@ -681,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. @@ -704,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{ @@ -727,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()] @@ -753,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 8f47c53d28617..81e4ef63c423b 100644 --- a/vendor/github.com/google/cel-go/interpreter/runtimecost.go +++ b/vendor/github.com/google/cel-go/interpreter/runtimecost.go @@ -15,6 +15,7 @@ package interpreter import ( + "errors" "math" "github.com/google/cel-go/common" @@ -34,78 +35,143 @@ type ActualCostEstimator interface { CallCost(function, overloadID string, args []ref.Val, result ref.Val) *uint64 } +// costTrackPlanOption modifies the cost tracking factory associatied with the CostObserver +type costTrackPlanOption func(*costTrackerFactory) *costTrackerFactory + +// CostTrackerFactory configures the factory method to generate a new cost-tracker per-evaluation. +func CostTrackerFactory(factory func() (*CostTracker, error)) costTrackPlanOption { + return func(fac *costTrackerFactory) *costTrackerFactory { + fac.factory = factory + return fac + } +} + // CostObserver provides an observer that tracks runtime cost. -func CostObserver(tracker *CostTracker) EvalObserver { - observer := func(id int64, programStep any, val ref.Val) { - switch t := programStep.(type) { - case ConstantQualifier: - // TODO: Push identifiers on to the stack before observing constant qualifiers that apply to them - // and enable the below pop. Once enabled this can case can be collapsed into the Qualifier case. - tracker.cost++ - case InterpretableConst: - // zero cost - case InterpretableAttribute: - switch a := t.Attr().(type) { - case *conditionalAttribute: - // Ternary has no direct cost. All cost is from the conditional and the true/false branch expressions. - tracker.stack.drop(a.falsy.ID(), a.truthy.ID(), a.expr.ID()) - default: - tracker.stack.drop(t.Attr().ID()) - tracker.cost += common.SelectAndIdentCost - } - if !tracker.presenceTestHasCost { - if _, isTestOnly := programStep.(*evalTestOnly); isTestOnly { - tracker.cost -= common.SelectAndIdentCost - } - } - case *evalExhaustiveConditional: - // Ternary has no direct cost. All cost is from the conditional and the true/false branch expressions. - tracker.stack.drop(t.attr.falsy.ID(), t.attr.truthy.ID(), t.attr.expr.ID()) +func CostObserver(opts ...costTrackPlanOption) PlannerOption { + ct := &costTrackerFactory{} + for _, o := range opts { + ct = o(ct) + } + return func(p *planner) (*planner, error) { + if ct.factory == nil { + return nil, errors.New("cost tracker factory not configured") + } + p.observers = append(p.observers, ct) + p.decorators = append(p.decorators, decObserveEval(ct.Observe)) + return p, nil + } +} - // While the field names are identical, the boolean operation eval structs do not share an interface and so - // must be handled individually. - case *evalOr: - for _, term := range t.terms { - tracker.stack.drop(term.ID()) - } - case *evalAnd: - for _, term := range t.terms { - tracker.stack.drop(term.ID()) - } - case *evalExhaustiveOr: - for _, term := range t.terms { - tracker.stack.drop(term.ID()) - } - case *evalExhaustiveAnd: - for _, term := range t.terms { - tracker.stack.drop(term.ID()) - } - case *evalFold: - tracker.stack.drop(t.iterRange.ID()) - case Qualifier: - tracker.cost++ - case InterpretableCall: - if argVals, ok := tracker.stack.dropArgs(t.Args()); ok { - tracker.cost += tracker.costCall(t, argVals, val) - } - case InterpretableConstructor: - tracker.stack.dropArgs(t.InitVals()) - switch t.Type() { - case types.ListType: - tracker.cost += common.ListCreateBaseCost - case types.MapType: - tracker.cost += common.MapCreateBaseCost - default: - tracker.cost += common.StructCreateBaseCost +// costTrackerFactory holds a factory for producing new CostTracker instances on each Eval call. +type costTrackerFactory struct { + factory func() (*CostTracker, error) +} + +// InitState produces a CostTracker and bundles it into an Activation in a way which is not visible +// to expression evaluation. +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 + } + 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(frame *ExecutionFrame) any { + if frame == nil || frame.ctx == 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) { + 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) { + case ConstantQualifier: + // TODO: Push identifiers on to the stack before observing constant qualifiers that apply to them + // and enable the below pop. Once enabled this can case can be collapsed into the Qualifier case. + tracker.cost++ + case InterpretableConst: + // zero cost + case InterpretableAttribute: + switch a := t.Attr().(type) { + case *conditionalAttribute: + // Ternary has no direct cost. All cost is from the conditional and the true/false branch expressions. + tracker.stack.drop(a.falsy.ID(), a.truthy.ID(), a.expr.ID()) + default: + tracker.stack.drop(t.Attr().ID()) + tracker.cost += common.SelectAndIdentCost + } + if !tracker.presenceTestHasCost { + if _, isTestOnly := programStep.(*evalTestOnly); isTestOnly { + tracker.cost -= common.SelectAndIdentCost } } - tracker.stack.push(val, id) + case *evalExhaustiveConditional: + // Ternary has no direct cost. All cost is from the conditional and the true/false branch expressions. + tracker.stack.drop(t.attr.falsy.ID(), t.attr.truthy.ID(), t.attr.expr.ID()) - if tracker.Limit != nil && tracker.cost > *tracker.Limit { - panic(EvalCancelledError{Cause: CostLimitExceeded, Message: "operation cancelled: actual cost limit exceeded"}) + // While the field names are identical, the boolean operation eval structs do not share an interface and so + // must be handled individually. + case *evalOr: + for _, term := range t.terms { + tracker.stack.drop(term.ID()) + } + case *evalAnd: + for _, term := range t.terms { + tracker.stack.drop(term.ID()) } + case *evalExhaustiveOr: + for _, term := range t.terms { + tracker.stack.drop(term.ID()) + } + case *evalExhaustiveAnd: + for _, term := range t.terms { + tracker.stack.drop(term.ID()) + } + case *evalFold: + tracker.stack.drop(t.iterRange.ID()) + case Qualifier: + tracker.cost++ + case InterpretableCall: + if argVals, ok := tracker.stack.dropArgs(t.Args()); ok { + tracker.cost += tracker.costCall(t, argVals, val) + } + case InterpretableConstructor: + tracker.stack.dropArgs(t.InitVals()) + switch t.Type() { + case types.ListType: + tracker.cost += common.ListCreateBaseCost + case types.MapType: + tracker.cost += common.MapCreateBaseCost + default: + tracker.cost += common.StructCreateBaseCost + } + } + tracker.stack.push(val, id) + + if tracker.Limit != nil && tracker.cost > *tracker.Limit { + panic(EvalCancelledError{Cause: CostLimitExceeded, Message: "operation cancelled: actual cost limit exceeded"}) } - return observer } // CostTrackerOption configures the behavior of CostTracker objects. @@ -170,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 @@ -181,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 } } @@ -189,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 } } @@ -197,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, @@ -212,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. @@ -233,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. @@ -247,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 @@ -302,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-- { @@ -318,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 c13296dd5cc58..84bef80d5f769 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/macro.go b/vendor/github.com/google/cel-go/parser/macro.go index 6b3b648d3438a..1ef43c4b5edc6 100644 --- a/vendor/github.com/google/cel-go/parser/macro.go +++ b/vendor/github.com/google/cel-go/parser/macro.go @@ -24,38 +24,74 @@ import ( "github.com/google/cel-go/common/types/ref" ) +// MacroOpt defines a functional option for configuring macro behavior. +type MacroOpt func(*macro) *macro + +// MacroDocs configures a list of strings into a multiline description for the macro. +func MacroDocs(docs ...string) MacroOpt { + return func(m *macro) *macro { + m.doc = common.MultilineDescription(docs...) + return m + } +} + +// MacroExamples configures a list of examples, either as a string or common.MultilineString, +// into an example set to be provided with the macro Documentation() call. +func MacroExamples(examples ...string) MacroOpt { + return func(m *macro) *macro { + m.examples = examples + return m + } +} + // NewGlobalMacro creates a Macro for a global function with the specified arg count. -func NewGlobalMacro(function string, argCount int, expander MacroExpander) Macro { - return ¯o{ +func NewGlobalMacro(function string, argCount int, expander MacroExpander, opts ...MacroOpt) Macro { + m := ¯o{ function: function, argCount: argCount, expander: expander} + for _, opt := range opts { + m = opt(m) + } + return m } // NewReceiverMacro creates a Macro for a receiver function matching the specified arg count. -func NewReceiverMacro(function string, argCount int, expander MacroExpander) Macro { - return ¯o{ +func NewReceiverMacro(function string, argCount int, expander MacroExpander, opts ...MacroOpt) Macro { + m := ¯o{ function: function, argCount: argCount, expander: expander, receiverStyle: true} + for _, opt := range opts { + m = opt(m) + } + return m } // NewGlobalVarArgMacro creates a Macro for a global function with a variable arg count. -func NewGlobalVarArgMacro(function string, expander MacroExpander) Macro { - return ¯o{ +func NewGlobalVarArgMacro(function string, expander MacroExpander, opts ...MacroOpt) Macro { + m := ¯o{ function: function, expander: expander, varArgStyle: true} + for _, opt := range opts { + m = opt(m) + } + return m } // NewReceiverVarArgMacro creates a Macro for a receiver function matching a variable arg count. -func NewReceiverVarArgMacro(function string, expander MacroExpander) Macro { - return ¯o{ +func NewReceiverVarArgMacro(function string, expander MacroExpander, opts ...MacroOpt) Macro { + m := ¯o{ function: function, expander: expander, receiverStyle: true, varArgStyle: true} + for _, opt := range opts { + m = opt(m) + } + return m } // Macro interface for describing the function signature to match and the MacroExpander to apply. @@ -95,6 +131,8 @@ type macro struct { varArgStyle bool argCount int expander MacroExpander + doc string + examples []string } // Function returns the macro's function name (i.e. the function whose syntax it mimics). @@ -125,6 +163,15 @@ func (m *macro) MacroKey() string { return makeMacroKey(m.function, m.argCount, m.receiverStyle) } +// Documentation generates documentation and examples for the macro. +func (m *macro) Documentation() *common.Doc { + examples := make([]*common.Doc, len(m.examples)) + for i, ex := range m.examples { + examples[i] = common.NewExampleDoc(ex) + } + return common.NewMacroDoc(m.Function(), m.doc, examples...) +} + func makeMacroKey(name string, args int, receiverStyle bool) string { return fmt.Sprintf("%s:%d:%v", name, args, receiverStyle) } @@ -250,37 +297,139 @@ type ExprHelper interface { var ( // HasMacro expands "has(m.f)" which tests the presence of a field, avoiding the need to // specify the field as a string. - HasMacro = NewGlobalMacro(operators.Has, 1, MakeHas) + HasMacro = NewGlobalMacro(operators.Has, 1, MakeHas, + MacroDocs( + `check a protocol buffer message for the presence of a field, or check a map`, + `for the presence of a string key.`, + `Only map accesses using the select notation are supported.`), + MacroExamples( + common.MultilineDescription( + `// true if the 'address' field exists in the 'user' message`, + `has(user.address)`), + common.MultilineDescription( + `// test whether the 'key_name' is set on the map which defines it`, + `has({'key_name': 'value'}.key_name) // true`), + common.MultilineDescription( + `// test whether the 'id' field is set to a non-default value on the Expr{} message literal`, + `has(Expr{}.id) // false`), + )) // AllMacro expands "range.all(var, predicate)" into a comprehension which ensures that all // elements in the range satisfy the predicate. - AllMacro = NewReceiverMacro(operators.All, 2, MakeAll) + AllMacro = NewReceiverMacro(operators.All, 2, MakeAll, + MacroDocs(`tests whether all elements in the input list or all keys in a map`, + `satisfy the given predicate. The all macro behaves in a manner consistent with`, + `the Logical AND operator including in how it absorbs errors and short-circuits.`), + MacroExamples( + `[1, 2, 3].all(x, x > 0) // true`, + `[1, 2, 0].all(x, x > 0) // false`, + `['apple', 'banana', 'cherry'].all(fruit, fruit.size() > 3) // true`, + `[3.14, 2.71, 1.61].all(num, num < 3.0) // false`, + `{'a': 1, 'b': 2, 'c': 3}.all(key, key != 'b') // false`, + common.MultilineDescription( + `// an empty list or map as the range will result in a trivially true result`, + `[].all(x, x > 0) // true`), + )) // ExistsMacro expands "range.exists(var, predicate)" into a comprehension which ensures that // some element in the range satisfies the predicate. - ExistsMacro = NewReceiverMacro(operators.Exists, 2, MakeExists) + ExistsMacro = NewReceiverMacro(operators.Exists, 2, MakeExists, + MacroDocs(`tests whether any value in the list or any key in the map`, + `satisfies the predicate expression. The exists macro behaves in a manner`, + `consistent with the Logical OR operator including in how it absorbs errors and`, + `short-circuits.`), + MacroExamples( + `[1, 2, 3].exists(i, i % 2 != 0) // true`, + `[0, -1, 5].exists(num, num < 0) // true`, + `{'x': 'foo', 'y': 'bar'}.exists(key, key.startsWith('z')) // false`, + common.MultilineDescription( + `// an empty list or map as the range will result in a trivially false result`, + `[].exists(i, i > 0) // false`), + common.MultilineDescription( + `// test whether a key name equalling 'iss' exists in the map and the`, + `// value contains the substring 'cel.dev'`, + `// tokens = {'sub': 'me', 'iss': 'https://issuer.cel.dev'}`, + `tokens.exists(k, k == 'iss' && tokens[k].contains('cel.dev'))`), + )) // ExistsOneMacro expands "range.exists_one(var, predicate)", which is true if for exactly one // element in range the predicate holds. // Deprecated: Use ExistsOneMacroNew - ExistsOneMacro = NewReceiverMacro(operators.ExistsOne, 2, MakeExistsOne) + ExistsOneMacro = NewReceiverMacro(operators.ExistsOne, 2, MakeExistsOne, + MacroDocs(`tests whether exactly one list element or map key satisfies`, + `the predicate expression. This macro does not short-circuit in order to remain`, + `consistent with logical operators being the only operators which can absorb`, + `errors within CEL.`), + MacroExamples( + `[1, 2, 2].exists_one(i, i < 2) // true`, + `{'a': 'hello', 'aa': 'hellohello'}.exists_one(k, k.startsWith('a')) // false`, + `[1, 2, 3, 4].exists_one(num, num % 2 == 0) // false`, + common.MultilineDescription( + `// ensure exactly one key in the map ends in @acme.co`, + `{'wiley@acme.co': 'coyote', 'aa@milne.co': 'bear'}.exists_one(k, k.endsWith('@acme.co')) // true`), + )) // ExistsOneMacroNew expands "range.existsOne(var, predicate)", which is true if for exactly one // element in range the predicate holds. - ExistsOneMacroNew = NewReceiverMacro("existsOne", 2, MakeExistsOne) + ExistsOneMacroNew = NewReceiverMacro("existsOne", 2, MakeExistsOne, + MacroDocs( + `tests whether exactly one list element or map key satisfies the predicate`, + `expression. This macro does not short-circuit in order to remain consistent`, + `with logical operators being the only operators which can absorb errors`, + `within CEL.`), + MacroExamples( + `[1, 2, 2].existsOne(i, i < 2) // true`, + `{'a': 'hello', 'aa': 'hellohello'}.existsOne(k, k.startsWith('a')) // false`, + `[1, 2, 3, 4].existsOne(num, num % 2 == 0) // false`, + common.MultilineDescription( + `// ensure exactly one key in the map ends in @acme.co`, + `{'wiley@acme.co': 'coyote', 'aa@milne.co': 'bear'}.existsOne(k, k.endsWith('@acme.co')) // true`), + )) // MapMacro expands "range.map(var, function)" into a comprehension which applies the function // to each element in the range to produce a new list. - MapMacro = NewReceiverMacro(operators.Map, 2, MakeMap) + MapMacro = NewReceiverMacro(operators.Map, 2, MakeMap, + MacroDocs("the three-argument form of map transforms all elements in the input range."), + MacroExamples( + `[1, 2, 3].map(x, x * 2) // [2, 4, 6]`, + `[5, 10, 15].map(x, x / 5) // [1, 2, 3]`, + `['apple', 'banana'].map(fruit, fruit.upperAscii()) // ['APPLE', 'BANANA']`, + common.MultilineDescription( + `// Combine all map key-value pairs into a list`, + `{'hi': 'you', 'howzit': 'bruv'}.map(k,`, + ` k + ":" + {'hi': 'you', 'howzit': 'bruv'}[k]) // ['hi:you', 'howzit:bruv']`), + )) // MapFilterMacro expands "range.map(var, predicate, function)" into a comprehension which // first filters the elements in the range by the predicate, then applies the transform function // to produce a new list. - MapFilterMacro = NewReceiverMacro(operators.Map, 3, MakeMap) + MapFilterMacro = NewReceiverMacro(operators.Map, 3, MakeMap, + MacroDocs(`the four-argument form of the map transforms only elements which satisfy`, + `the predicate which is equivalent to chaining the filter and three-argument`, + `map macros together.`), + MacroExamples( + common.MultilineDescription( + `// multiply only numbers divisible two, by 2`, + `[1, 2, 3, 4].map(num, num % 2 == 0, num * 2) // [4, 8]`), + )) // FilterMacro expands "range.filter(var, predicate)" into a comprehension which filters // elements in the range, producing a new list from the elements that satisfy the predicate. - FilterMacro = NewReceiverMacro(operators.Filter, 2, MakeFilter) + FilterMacro = NewReceiverMacro(operators.Filter, 2, MakeFilter, + MacroDocs(`returns a list containing only the elements from the input list`, + `that satisfy the given predicate`), + MacroExamples( + `[1, 2, 3].filter(x, x > 1) // [2, 3]`, + `['cat', 'dog', 'bird', 'fish'].filter(pet, pet.size() == 3) // ['cat', 'dog']`, + `[{'a': 10, 'b': 5, 'c': 20}].map(m, m.filter(key, m[key] > 10)) // [['c']]`, + common.MultilineDescription( + `// filter a list to select only emails with the @cel.dev suffix`, + `['alice@buf.io', 'tristan@cel.dev'].filter(v, v.endsWith('@cel.dev')) // ['tristan@cel.dev']`), + common.MultilineDescription( + `// filter a map into a list, selecting only the values for keys that start with 'http-auth'`, + `{'http-auth-agent': 'secret', 'user-agent': 'mozilla'}.filter(k,`, + ` k.startsWith('http-auth')) // ['secret']`), + )) // AllMacros includes the list of all spec-supported macros. AllMacros = []Macro{ diff --git a/vendor/github.com/google/cel-go/parser/options.go b/vendor/github.com/google/cel-go/parser/options.go index 4eb30f83e05c9..281021f122024 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 b5ec73ec64a0b..338233543d694 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 ffd5b18e471f8..d503a450e9df3 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/stoewer/go-strcase/.gitignore b/vendor/github.com/stoewer/go-strcase/.gitignore deleted file mode 100644 index db5247b9449c0..0000000000000 --- 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 7f98d55c42492..0000000000000 --- 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 a105a3819a1cf..0000000000000 --- 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 84a640e714091..0000000000000 --- 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 ff9e66e0ce139..0000000000000 --- 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 3e441ca3ef330..0000000000000 --- 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 ecad589143934..0000000000000 --- 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 e9a6487579dc1..0000000000000 --- 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 1b216e20cf40f..0000000000000 --- 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/go.yaml.in/yaml/v3/LICENSE b/vendor/go.yaml.in/yaml/v3/LICENSE new file mode 100644 index 0000000000000..2683e4bb1f24c --- /dev/null +++ b/vendor/go.yaml.in/yaml/v3/LICENSE @@ -0,0 +1,50 @@ + +This project is covered by two different licenses: MIT and Apache. + +#### MIT License #### + +The following files were ported to Go from C files of libyaml, and thus +are still covered by their original MIT license, with the additional +copyright staring in 2011 when the project was ported over: + + apic.go emitterc.go parserc.go readerc.go scannerc.go + writerc.go yamlh.go yamlprivateh.go + +Copyright (c) 2006-2010 Kirill Simonov +Copyright (c) 2006-2011 Kirill Simonov + +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. + +### Apache License ### + +All the remaining project files are covered by the Apache license: + +Copyright (c) 2011-2019 Canonical Ltd + +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/go.yaml.in/yaml/v3/NOTICE b/vendor/go.yaml.in/yaml/v3/NOTICE new file mode 100644 index 0000000000000..866d74a7ad791 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v3/NOTICE @@ -0,0 +1,13 @@ +Copyright 2011-2016 Canonical Ltd. + +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/go.yaml.in/yaml/v3/README.md b/vendor/go.yaml.in/yaml/v3/README.md new file mode 100644 index 0000000000000..15a85a6350a95 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v3/README.md @@ -0,0 +1,171 @@ +go.yaml.in/yaml +=============== + +YAML Support for the Go Language + + +## Introduction + +The `yaml` package enables [Go](https://go.dev/) programs to comfortably encode +and decode [YAML](https://yaml.org/) values. + +It was originally developed within [Canonical](https://www.canonical.com) as +part of the [juju](https://juju.ubuntu.com) project, and is based on a pure Go +port of the well-known [libyaml](http://pyyaml.org/wiki/LibYAML) C library to +parse and generate YAML data quickly and reliably. + + +## Project Status + +This project started as a fork of the extremely popular [go-yaml]( +https://github.com/go-yaml/yaml/) +project, and is being maintained by the official [YAML organization]( +https://github.com/yaml/). + +The YAML team took over ongoing maintenance and development of the project after +discussion with go-yaml's author, @niemeyer, following his decision to +[label the project repository as "unmaintained"]( +https://github.com/go-yaml/yaml/blob/944c86a7d2/README.md) in April 2025. + +We have put together a team of dedicated maintainers including representatives +of go-yaml's most important downstream projects. + +We will strive to earn the trust of the various go-yaml forks to switch back to +this repository as their upstream. + +Please [contact us](https://cloud-native.slack.com/archives/C08PPAT8PS7) if you +would like to contribute or be involved. + + +## Compatibility + +The `yaml` package supports most of YAML 1.2, but preserves some behavior from +1.1 for backwards compatibility. + +Specifically, v3 of the `yaml` package: + +* Supports YAML 1.1 bools (`yes`/`no`, `on`/`off`) as long as they are being + decoded into a typed bool value. + Otherwise they behave as a string. + Booleans in YAML 1.2 are `true`/`false` only. +* Supports octals encoded and decoded as `0777` per YAML 1.1, rather than + `0o777` as specified in YAML 1.2, because most parsers still use the old + format. + Octals in the `0o777` format are supported though, so new files work. +* Does not support base-60 floats. + These are gone from YAML 1.2, and were actually never supported by this + package as it's clearly a poor choice. + + +## Installation and Usage + +The import path for the package is *go.yaml.in/yaml/v3*. + +To install it, run: + +```bash +go get go.yaml.in/yaml/v3 +``` + + +## API Documentation + +See: + + +## API Stability + +The package API for yaml v3 will remain stable as described in [gopkg.in]( +https://gopkg.in). + + +## Example + +```go +package main + +import ( + "fmt" + "log" + + "go.yaml.in/yaml/v3" +) + +var data = ` +a: Easy! +b: + c: 2 + d: [3, 4] +` + +// Note: struct fields must be public in order for unmarshal to +// correctly populate the data. +type T struct { + A string + B struct { + RenamedC int `yaml:"c"` + D []int `yaml:",flow"` + } +} + +func main() { + t := T{} + + err := yaml.Unmarshal([]byte(data), &t) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- t:\n%v\n\n", t) + + d, err := yaml.Marshal(&t) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- t dump:\n%s\n\n", string(d)) + + m := make(map[interface{}]interface{}) + + err = yaml.Unmarshal([]byte(data), &m) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- m:\n%v\n\n", m) + + d, err = yaml.Marshal(&m) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- m dump:\n%s\n\n", string(d)) +} +``` + +This example will generate the following output: + +``` +--- t: +{Easy! {2 [3 4]}} + +--- t dump: +a: Easy! +b: + c: 2 + d: [3, 4] + + +--- m: +map[a:Easy! b:map[c:2 d:[3 4]]] + +--- m dump: +a: Easy! +b: + c: 2 + d: + - 3 + - 4 +``` + + +## License + +The yaml package is licensed under the MIT and Apache License 2.0 licenses. +Please see the LICENSE file for details. diff --git a/vendor/go.yaml.in/yaml/v3/apic.go b/vendor/go.yaml.in/yaml/v3/apic.go new file mode 100644 index 0000000000000..05fd305da1658 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v3/apic.go @@ -0,0 +1,747 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// Copyright (c) 2006-2010 Kirill Simonov +// +// 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. + +package yaml + +import ( + "io" +) + +func yaml_insert_token(parser *yaml_parser_t, pos int, token *yaml_token_t) { + //fmt.Println("yaml_insert_token", "pos:", pos, "typ:", token.typ, "head:", parser.tokens_head, "len:", len(parser.tokens)) + + // Check if we can move the queue at the beginning of the buffer. + if parser.tokens_head > 0 && len(parser.tokens) == cap(parser.tokens) { + if parser.tokens_head != len(parser.tokens) { + copy(parser.tokens, parser.tokens[parser.tokens_head:]) + } + parser.tokens = parser.tokens[:len(parser.tokens)-parser.tokens_head] + parser.tokens_head = 0 + } + parser.tokens = append(parser.tokens, *token) + if pos < 0 { + return + } + copy(parser.tokens[parser.tokens_head+pos+1:], parser.tokens[parser.tokens_head+pos:]) + parser.tokens[parser.tokens_head+pos] = *token +} + +// Create a new parser object. +func yaml_parser_initialize(parser *yaml_parser_t) bool { + *parser = yaml_parser_t{ + raw_buffer: make([]byte, 0, input_raw_buffer_size), + buffer: make([]byte, 0, input_buffer_size), + } + return true +} + +// Destroy a parser object. +func yaml_parser_delete(parser *yaml_parser_t) { + *parser = yaml_parser_t{} +} + +// String read handler. +func yaml_string_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) { + if parser.input_pos == len(parser.input) { + return 0, io.EOF + } + n = copy(buffer, parser.input[parser.input_pos:]) + parser.input_pos += n + return n, nil +} + +// Reader read handler. +func yaml_reader_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) { + return parser.input_reader.Read(buffer) +} + +// Set a string input. +func yaml_parser_set_input_string(parser *yaml_parser_t, input []byte) { + if parser.read_handler != nil { + panic("must set the input source only once") + } + parser.read_handler = yaml_string_read_handler + parser.input = input + parser.input_pos = 0 +} + +// Set a file input. +func yaml_parser_set_input_reader(parser *yaml_parser_t, r io.Reader) { + if parser.read_handler != nil { + panic("must set the input source only once") + } + parser.read_handler = yaml_reader_read_handler + parser.input_reader = r +} + +// Set the source encoding. +func yaml_parser_set_encoding(parser *yaml_parser_t, encoding yaml_encoding_t) { + if parser.encoding != yaml_ANY_ENCODING { + panic("must set the encoding only once") + } + parser.encoding = encoding +} + +// Create a new emitter object. +func yaml_emitter_initialize(emitter *yaml_emitter_t) { + *emitter = yaml_emitter_t{ + buffer: make([]byte, output_buffer_size), + raw_buffer: make([]byte, 0, output_raw_buffer_size), + states: make([]yaml_emitter_state_t, 0, initial_stack_size), + events: make([]yaml_event_t, 0, initial_queue_size), + best_width: -1, + } +} + +// Destroy an emitter object. +func yaml_emitter_delete(emitter *yaml_emitter_t) { + *emitter = yaml_emitter_t{} +} + +// String write handler. +func yaml_string_write_handler(emitter *yaml_emitter_t, buffer []byte) error { + *emitter.output_buffer = append(*emitter.output_buffer, buffer...) + return nil +} + +// yaml_writer_write_handler uses emitter.output_writer to write the +// emitted text. +func yaml_writer_write_handler(emitter *yaml_emitter_t, buffer []byte) error { + _, err := emitter.output_writer.Write(buffer) + return err +} + +// Set a string output. +func yaml_emitter_set_output_string(emitter *yaml_emitter_t, output_buffer *[]byte) { + if emitter.write_handler != nil { + panic("must set the output target only once") + } + emitter.write_handler = yaml_string_write_handler + emitter.output_buffer = output_buffer +} + +// Set a file output. +func yaml_emitter_set_output_writer(emitter *yaml_emitter_t, w io.Writer) { + if emitter.write_handler != nil { + panic("must set the output target only once") + } + emitter.write_handler = yaml_writer_write_handler + emitter.output_writer = w +} + +// Set the output encoding. +func yaml_emitter_set_encoding(emitter *yaml_emitter_t, encoding yaml_encoding_t) { + if emitter.encoding != yaml_ANY_ENCODING { + panic("must set the output encoding only once") + } + emitter.encoding = encoding +} + +// Set the canonical output style. +func yaml_emitter_set_canonical(emitter *yaml_emitter_t, canonical bool) { + emitter.canonical = canonical +} + +// Set the indentation increment. +func yaml_emitter_set_indent(emitter *yaml_emitter_t, indent int) { + if indent < 2 || indent > 9 { + indent = 2 + } + emitter.best_indent = indent +} + +// Set the preferred line width. +func yaml_emitter_set_width(emitter *yaml_emitter_t, width int) { + if width < 0 { + width = -1 + } + emitter.best_width = width +} + +// Set if unescaped non-ASCII characters are allowed. +func yaml_emitter_set_unicode(emitter *yaml_emitter_t, unicode bool) { + emitter.unicode = unicode +} + +// Set the preferred line break character. +func yaml_emitter_set_break(emitter *yaml_emitter_t, line_break yaml_break_t) { + emitter.line_break = line_break +} + +///* +// * Destroy a token object. +// */ +// +//YAML_DECLARE(void) +//yaml_token_delete(yaml_token_t *token) +//{ +// assert(token); // Non-NULL token object expected. +// +// switch (token.type) +// { +// case YAML_TAG_DIRECTIVE_TOKEN: +// yaml_free(token.data.tag_directive.handle); +// yaml_free(token.data.tag_directive.prefix); +// break; +// +// case YAML_ALIAS_TOKEN: +// yaml_free(token.data.alias.value); +// break; +// +// case YAML_ANCHOR_TOKEN: +// yaml_free(token.data.anchor.value); +// break; +// +// case YAML_TAG_TOKEN: +// yaml_free(token.data.tag.handle); +// yaml_free(token.data.tag.suffix); +// break; +// +// case YAML_SCALAR_TOKEN: +// yaml_free(token.data.scalar.value); +// break; +// +// default: +// break; +// } +// +// memset(token, 0, sizeof(yaml_token_t)); +//} +// +///* +// * Check if a string is a valid UTF-8 sequence. +// * +// * Check 'reader.c' for more details on UTF-8 encoding. +// */ +// +//static int +//yaml_check_utf8(yaml_char_t *start, size_t length) +//{ +// yaml_char_t *end = start+length; +// yaml_char_t *pointer = start; +// +// while (pointer < end) { +// unsigned char octet; +// unsigned int width; +// unsigned int value; +// size_t k; +// +// octet = pointer[0]; +// width = (octet & 0x80) == 0x00 ? 1 : +// (octet & 0xE0) == 0xC0 ? 2 : +// (octet & 0xF0) == 0xE0 ? 3 : +// (octet & 0xF8) == 0xF0 ? 4 : 0; +// value = (octet & 0x80) == 0x00 ? octet & 0x7F : +// (octet & 0xE0) == 0xC0 ? octet & 0x1F : +// (octet & 0xF0) == 0xE0 ? octet & 0x0F : +// (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0; +// if (!width) return 0; +// if (pointer+width > end) return 0; +// for (k = 1; k < width; k ++) { +// octet = pointer[k]; +// if ((octet & 0xC0) != 0x80) return 0; +// value = (value << 6) + (octet & 0x3F); +// } +// if (!((width == 1) || +// (width == 2 && value >= 0x80) || +// (width == 3 && value >= 0x800) || +// (width == 4 && value >= 0x10000))) return 0; +// +// pointer += width; +// } +// +// return 1; +//} +// + +// Create STREAM-START. +func yaml_stream_start_event_initialize(event *yaml_event_t, encoding yaml_encoding_t) { + *event = yaml_event_t{ + typ: yaml_STREAM_START_EVENT, + encoding: encoding, + } +} + +// Create STREAM-END. +func yaml_stream_end_event_initialize(event *yaml_event_t) { + *event = yaml_event_t{ + typ: yaml_STREAM_END_EVENT, + } +} + +// Create DOCUMENT-START. +func yaml_document_start_event_initialize( + event *yaml_event_t, + version_directive *yaml_version_directive_t, + tag_directives []yaml_tag_directive_t, + implicit bool, +) { + *event = yaml_event_t{ + typ: yaml_DOCUMENT_START_EVENT, + version_directive: version_directive, + tag_directives: tag_directives, + implicit: implicit, + } +} + +// Create DOCUMENT-END. +func yaml_document_end_event_initialize(event *yaml_event_t, implicit bool) { + *event = yaml_event_t{ + typ: yaml_DOCUMENT_END_EVENT, + implicit: implicit, + } +} + +// Create ALIAS. +func yaml_alias_event_initialize(event *yaml_event_t, anchor []byte) bool { + *event = yaml_event_t{ + typ: yaml_ALIAS_EVENT, + anchor: anchor, + } + return true +} + +// Create SCALAR. +func yaml_scalar_event_initialize(event *yaml_event_t, anchor, tag, value []byte, plain_implicit, quoted_implicit bool, style yaml_scalar_style_t) bool { + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + anchor: anchor, + tag: tag, + value: value, + implicit: plain_implicit, + quoted_implicit: quoted_implicit, + style: yaml_style_t(style), + } + return true +} + +// Create SEQUENCE-START. +func yaml_sequence_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_sequence_style_t) bool { + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(style), + } + return true +} + +// Create SEQUENCE-END. +func yaml_sequence_end_event_initialize(event *yaml_event_t) bool { + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + } + return true +} + +// Create MAPPING-START. +func yaml_mapping_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_mapping_style_t) { + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(style), + } +} + +// Create MAPPING-END. +func yaml_mapping_end_event_initialize(event *yaml_event_t) { + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + } +} + +// Destroy an event object. +func yaml_event_delete(event *yaml_event_t) { + *event = yaml_event_t{} +} + +///* +// * Create a document object. +// */ +// +//YAML_DECLARE(int) +//yaml_document_initialize(document *yaml_document_t, +// version_directive *yaml_version_directive_t, +// tag_directives_start *yaml_tag_directive_t, +// tag_directives_end *yaml_tag_directive_t, +// start_implicit int, end_implicit int) +//{ +// struct { +// error yaml_error_type_t +// } context +// struct { +// start *yaml_node_t +// end *yaml_node_t +// top *yaml_node_t +// } nodes = { NULL, NULL, NULL } +// version_directive_copy *yaml_version_directive_t = NULL +// struct { +// start *yaml_tag_directive_t +// end *yaml_tag_directive_t +// top *yaml_tag_directive_t +// } tag_directives_copy = { NULL, NULL, NULL } +// value yaml_tag_directive_t = { NULL, NULL } +// mark yaml_mark_t = { 0, 0, 0 } +// +// assert(document) // Non-NULL document object is expected. +// assert((tag_directives_start && tag_directives_end) || +// (tag_directives_start == tag_directives_end)) +// // Valid tag directives are expected. +// +// if (!STACK_INIT(&context, nodes, INITIAL_STACK_SIZE)) goto error +// +// if (version_directive) { +// version_directive_copy = yaml_malloc(sizeof(yaml_version_directive_t)) +// if (!version_directive_copy) goto error +// version_directive_copy.major = version_directive.major +// version_directive_copy.minor = version_directive.minor +// } +// +// if (tag_directives_start != tag_directives_end) { +// tag_directive *yaml_tag_directive_t +// if (!STACK_INIT(&context, tag_directives_copy, INITIAL_STACK_SIZE)) +// goto error +// for (tag_directive = tag_directives_start +// tag_directive != tag_directives_end; tag_directive ++) { +// assert(tag_directive.handle) +// assert(tag_directive.prefix) +// if (!yaml_check_utf8(tag_directive.handle, +// strlen((char *)tag_directive.handle))) +// goto error +// if (!yaml_check_utf8(tag_directive.prefix, +// strlen((char *)tag_directive.prefix))) +// goto error +// value.handle = yaml_strdup(tag_directive.handle) +// value.prefix = yaml_strdup(tag_directive.prefix) +// if (!value.handle || !value.prefix) goto error +// if (!PUSH(&context, tag_directives_copy, value)) +// goto error +// value.handle = NULL +// value.prefix = NULL +// } +// } +// +// DOCUMENT_INIT(*document, nodes.start, nodes.end, version_directive_copy, +// tag_directives_copy.start, tag_directives_copy.top, +// start_implicit, end_implicit, mark, mark) +// +// return 1 +// +//error: +// STACK_DEL(&context, nodes) +// yaml_free(version_directive_copy) +// while (!STACK_EMPTY(&context, tag_directives_copy)) { +// value yaml_tag_directive_t = POP(&context, tag_directives_copy) +// yaml_free(value.handle) +// yaml_free(value.prefix) +// } +// STACK_DEL(&context, tag_directives_copy) +// yaml_free(value.handle) +// yaml_free(value.prefix) +// +// return 0 +//} +// +///* +// * Destroy a document object. +// */ +// +//YAML_DECLARE(void) +//yaml_document_delete(document *yaml_document_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// tag_directive *yaml_tag_directive_t +// +// context.error = YAML_NO_ERROR // Eliminate a compiler warning. +// +// assert(document) // Non-NULL document object is expected. +// +// while (!STACK_EMPTY(&context, document.nodes)) { +// node yaml_node_t = POP(&context, document.nodes) +// yaml_free(node.tag) +// switch (node.type) { +// case YAML_SCALAR_NODE: +// yaml_free(node.data.scalar.value) +// break +// case YAML_SEQUENCE_NODE: +// STACK_DEL(&context, node.data.sequence.items) +// break +// case YAML_MAPPING_NODE: +// STACK_DEL(&context, node.data.mapping.pairs) +// break +// default: +// assert(0) // Should not happen. +// } +// } +// STACK_DEL(&context, document.nodes) +// +// yaml_free(document.version_directive) +// for (tag_directive = document.tag_directives.start +// tag_directive != document.tag_directives.end +// tag_directive++) { +// yaml_free(tag_directive.handle) +// yaml_free(tag_directive.prefix) +// } +// yaml_free(document.tag_directives.start) +// +// memset(document, 0, sizeof(yaml_document_t)) +//} +// +///** +// * Get a document node. +// */ +// +//YAML_DECLARE(yaml_node_t *) +//yaml_document_get_node(document *yaml_document_t, index int) +//{ +// assert(document) // Non-NULL document object is expected. +// +// if (index > 0 && document.nodes.start + index <= document.nodes.top) { +// return document.nodes.start + index - 1 +// } +// return NULL +//} +// +///** +// * Get the root object. +// */ +// +//YAML_DECLARE(yaml_node_t *) +//yaml_document_get_root_node(document *yaml_document_t) +//{ +// assert(document) // Non-NULL document object is expected. +// +// if (document.nodes.top != document.nodes.start) { +// return document.nodes.start +// } +// return NULL +//} +// +///* +// * Add a scalar node to a document. +// */ +// +//YAML_DECLARE(int) +//yaml_document_add_scalar(document *yaml_document_t, +// tag *yaml_char_t, value *yaml_char_t, length int, +// style yaml_scalar_style_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// mark yaml_mark_t = { 0, 0, 0 } +// tag_copy *yaml_char_t = NULL +// value_copy *yaml_char_t = NULL +// node yaml_node_t +// +// assert(document) // Non-NULL document object is expected. +// assert(value) // Non-NULL value is expected. +// +// if (!tag) { +// tag = (yaml_char_t *)YAML_DEFAULT_SCALAR_TAG +// } +// +// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error +// tag_copy = yaml_strdup(tag) +// if (!tag_copy) goto error +// +// if (length < 0) { +// length = strlen((char *)value) +// } +// +// if (!yaml_check_utf8(value, length)) goto error +// value_copy = yaml_malloc(length+1) +// if (!value_copy) goto error +// memcpy(value_copy, value, length) +// value_copy[length] = '\0' +// +// SCALAR_NODE_INIT(node, tag_copy, value_copy, length, style, mark, mark) +// if (!PUSH(&context, document.nodes, node)) goto error +// +// return document.nodes.top - document.nodes.start +// +//error: +// yaml_free(tag_copy) +// yaml_free(value_copy) +// +// return 0 +//} +// +///* +// * Add a sequence node to a document. +// */ +// +//YAML_DECLARE(int) +//yaml_document_add_sequence(document *yaml_document_t, +// tag *yaml_char_t, style yaml_sequence_style_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// mark yaml_mark_t = { 0, 0, 0 } +// tag_copy *yaml_char_t = NULL +// struct { +// start *yaml_node_item_t +// end *yaml_node_item_t +// top *yaml_node_item_t +// } items = { NULL, NULL, NULL } +// node yaml_node_t +// +// assert(document) // Non-NULL document object is expected. +// +// if (!tag) { +// tag = (yaml_char_t *)YAML_DEFAULT_SEQUENCE_TAG +// } +// +// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error +// tag_copy = yaml_strdup(tag) +// if (!tag_copy) goto error +// +// if (!STACK_INIT(&context, items, INITIAL_STACK_SIZE)) goto error +// +// SEQUENCE_NODE_INIT(node, tag_copy, items.start, items.end, +// style, mark, mark) +// if (!PUSH(&context, document.nodes, node)) goto error +// +// return document.nodes.top - document.nodes.start +// +//error: +// STACK_DEL(&context, items) +// yaml_free(tag_copy) +// +// return 0 +//} +// +///* +// * Add a mapping node to a document. +// */ +// +//YAML_DECLARE(int) +//yaml_document_add_mapping(document *yaml_document_t, +// tag *yaml_char_t, style yaml_mapping_style_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// mark yaml_mark_t = { 0, 0, 0 } +// tag_copy *yaml_char_t = NULL +// struct { +// start *yaml_node_pair_t +// end *yaml_node_pair_t +// top *yaml_node_pair_t +// } pairs = { NULL, NULL, NULL } +// node yaml_node_t +// +// assert(document) // Non-NULL document object is expected. +// +// if (!tag) { +// tag = (yaml_char_t *)YAML_DEFAULT_MAPPING_TAG +// } +// +// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error +// tag_copy = yaml_strdup(tag) +// if (!tag_copy) goto error +// +// if (!STACK_INIT(&context, pairs, INITIAL_STACK_SIZE)) goto error +// +// MAPPING_NODE_INIT(node, tag_copy, pairs.start, pairs.end, +// style, mark, mark) +// if (!PUSH(&context, document.nodes, node)) goto error +// +// return document.nodes.top - document.nodes.start +// +//error: +// STACK_DEL(&context, pairs) +// yaml_free(tag_copy) +// +// return 0 +//} +// +///* +// * Append an item to a sequence node. +// */ +// +//YAML_DECLARE(int) +//yaml_document_append_sequence_item(document *yaml_document_t, +// sequence int, item int) +//{ +// struct { +// error yaml_error_type_t +// } context +// +// assert(document) // Non-NULL document is required. +// assert(sequence > 0 +// && document.nodes.start + sequence <= document.nodes.top) +// // Valid sequence id is required. +// assert(document.nodes.start[sequence-1].type == YAML_SEQUENCE_NODE) +// // A sequence node is required. +// assert(item > 0 && document.nodes.start + item <= document.nodes.top) +// // Valid item id is required. +// +// if (!PUSH(&context, +// document.nodes.start[sequence-1].data.sequence.items, item)) +// return 0 +// +// return 1 +//} +// +///* +// * Append a pair of a key and a value to a mapping node. +// */ +// +//YAML_DECLARE(int) +//yaml_document_append_mapping_pair(document *yaml_document_t, +// mapping int, key int, value int) +//{ +// struct { +// error yaml_error_type_t +// } context +// +// pair yaml_node_pair_t +// +// assert(document) // Non-NULL document is required. +// assert(mapping > 0 +// && document.nodes.start + mapping <= document.nodes.top) +// // Valid mapping id is required. +// assert(document.nodes.start[mapping-1].type == YAML_MAPPING_NODE) +// // A mapping node is required. +// assert(key > 0 && document.nodes.start + key <= document.nodes.top) +// // Valid key id is required. +// assert(value > 0 && document.nodes.start + value <= document.nodes.top) +// // Valid value id is required. +// +// pair.key = key +// pair.value = value +// +// if (!PUSH(&context, +// document.nodes.start[mapping-1].data.mapping.pairs, pair)) +// return 0 +// +// return 1 +//} +// +// diff --git a/vendor/go.yaml.in/yaml/v3/decode.go b/vendor/go.yaml.in/yaml/v3/decode.go new file mode 100644 index 0000000000000..02e2b17bfe0fb --- /dev/null +++ b/vendor/go.yaml.in/yaml/v3/decode.go @@ -0,0 +1,1018 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// +// 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 yaml + +import ( + "encoding" + "encoding/base64" + "fmt" + "io" + "math" + "reflect" + "strconv" + "time" +) + +// ---------------------------------------------------------------------------- +// Parser, produces a node tree out of a libyaml event stream. + +type parser struct { + parser yaml_parser_t + event yaml_event_t + doc *Node + anchors map[string]*Node + doneInit bool + textless bool +} + +func newParser(b []byte) *parser { + p := parser{} + if !yaml_parser_initialize(&p.parser) { + panic("failed to initialize YAML emitter") + } + if len(b) == 0 { + b = []byte{'\n'} + } + yaml_parser_set_input_string(&p.parser, b) + return &p +} + +func newParserFromReader(r io.Reader) *parser { + p := parser{} + if !yaml_parser_initialize(&p.parser) { + panic("failed to initialize YAML emitter") + } + yaml_parser_set_input_reader(&p.parser, r) + return &p +} + +func (p *parser) init() { + if p.doneInit { + return + } + p.anchors = make(map[string]*Node) + p.expect(yaml_STREAM_START_EVENT) + p.doneInit = true +} + +func (p *parser) destroy() { + if p.event.typ != yaml_NO_EVENT { + yaml_event_delete(&p.event) + } + yaml_parser_delete(&p.parser) +} + +// expect consumes an event from the event stream and +// checks that it's of the expected type. +func (p *parser) expect(e yaml_event_type_t) { + if p.event.typ == yaml_NO_EVENT { + if !yaml_parser_parse(&p.parser, &p.event) { + p.fail() + } + } + if p.event.typ == yaml_STREAM_END_EVENT { + failf("attempted to go past the end of stream; corrupted value?") + } + if p.event.typ != e { + p.parser.problem = fmt.Sprintf("expected %s event but got %s", e, p.event.typ) + p.fail() + } + yaml_event_delete(&p.event) + p.event.typ = yaml_NO_EVENT +} + +// peek peeks at the next event in the event stream, +// puts the results into p.event and returns the event type. +func (p *parser) peek() yaml_event_type_t { + if p.event.typ != yaml_NO_EVENT { + return p.event.typ + } + // It's curious choice from the underlying API to generally return a + // positive result on success, but on this case return true in an error + // scenario. This was the source of bugs in the past (issue #666). + if !yaml_parser_parse(&p.parser, &p.event) || p.parser.error != yaml_NO_ERROR { + p.fail() + } + return p.event.typ +} + +func (p *parser) fail() { + var where string + var line int + if p.parser.context_mark.line != 0 { + line = p.parser.context_mark.line + // Scanner errors don't iterate line before returning error + if p.parser.error == yaml_SCANNER_ERROR { + line++ + } + } else if p.parser.problem_mark.line != 0 { + line = p.parser.problem_mark.line + // Scanner errors don't iterate line before returning error + if p.parser.error == yaml_SCANNER_ERROR { + line++ + } + } + if line != 0 { + where = "line " + strconv.Itoa(line) + ": " + } + var msg string + if len(p.parser.problem) > 0 { + msg = p.parser.problem + } else { + msg = "unknown problem parsing YAML content" + } + failf("%s%s", where, msg) +} + +func (p *parser) anchor(n *Node, anchor []byte) { + if anchor != nil { + n.Anchor = string(anchor) + p.anchors[n.Anchor] = n + } +} + +func (p *parser) parse() *Node { + p.init() + switch p.peek() { + case yaml_SCALAR_EVENT: + return p.scalar() + case yaml_ALIAS_EVENT: + return p.alias() + case yaml_MAPPING_START_EVENT: + return p.mapping() + case yaml_SEQUENCE_START_EVENT: + return p.sequence() + case yaml_DOCUMENT_START_EVENT: + return p.document() + case yaml_STREAM_END_EVENT: + // Happens when attempting to decode an empty buffer. + return nil + case yaml_TAIL_COMMENT_EVENT: + panic("internal error: unexpected tail comment event (please report)") + default: + panic("internal error: attempted to parse unknown event (please report): " + p.event.typ.String()) + } +} + +func (p *parser) node(kind Kind, defaultTag, tag, value string) *Node { + var style Style + if tag != "" && tag != "!" { + tag = shortTag(tag) + style = TaggedStyle + } else if defaultTag != "" { + tag = defaultTag + } else if kind == ScalarNode { + tag, _ = resolve("", value) + } + n := &Node{ + Kind: kind, + Tag: tag, + Value: value, + Style: style, + } + if !p.textless { + n.Line = p.event.start_mark.line + 1 + n.Column = p.event.start_mark.column + 1 + n.HeadComment = string(p.event.head_comment) + n.LineComment = string(p.event.line_comment) + n.FootComment = string(p.event.foot_comment) + } + return n +} + +func (p *parser) parseChild(parent *Node) *Node { + child := p.parse() + parent.Content = append(parent.Content, child) + return child +} + +func (p *parser) document() *Node { + n := p.node(DocumentNode, "", "", "") + p.doc = n + p.expect(yaml_DOCUMENT_START_EVENT) + p.parseChild(n) + if p.peek() == yaml_DOCUMENT_END_EVENT { + n.FootComment = string(p.event.foot_comment) + } + p.expect(yaml_DOCUMENT_END_EVENT) + return n +} + +func (p *parser) alias() *Node { + n := p.node(AliasNode, "", "", string(p.event.anchor)) + n.Alias = p.anchors[n.Value] + if n.Alias == nil { + failf("unknown anchor '%s' referenced", n.Value) + } + p.expect(yaml_ALIAS_EVENT) + return n +} + +func (p *parser) scalar() *Node { + var parsedStyle = p.event.scalar_style() + var nodeStyle Style + switch { + case parsedStyle&yaml_DOUBLE_QUOTED_SCALAR_STYLE != 0: + nodeStyle = DoubleQuotedStyle + case parsedStyle&yaml_SINGLE_QUOTED_SCALAR_STYLE != 0: + nodeStyle = SingleQuotedStyle + case parsedStyle&yaml_LITERAL_SCALAR_STYLE != 0: + nodeStyle = LiteralStyle + case parsedStyle&yaml_FOLDED_SCALAR_STYLE != 0: + nodeStyle = FoldedStyle + } + var nodeValue = string(p.event.value) + var nodeTag = string(p.event.tag) + var defaultTag string + if nodeStyle == 0 { + if nodeValue == "<<" { + defaultTag = mergeTag + } + } else { + defaultTag = strTag + } + n := p.node(ScalarNode, defaultTag, nodeTag, nodeValue) + n.Style |= nodeStyle + p.anchor(n, p.event.anchor) + p.expect(yaml_SCALAR_EVENT) + return n +} + +func (p *parser) sequence() *Node { + n := p.node(SequenceNode, seqTag, string(p.event.tag), "") + if p.event.sequence_style()&yaml_FLOW_SEQUENCE_STYLE != 0 { + n.Style |= FlowStyle + } + p.anchor(n, p.event.anchor) + p.expect(yaml_SEQUENCE_START_EVENT) + for p.peek() != yaml_SEQUENCE_END_EVENT { + p.parseChild(n) + } + n.LineComment = string(p.event.line_comment) + n.FootComment = string(p.event.foot_comment) + p.expect(yaml_SEQUENCE_END_EVENT) + return n +} + +func (p *parser) mapping() *Node { + n := p.node(MappingNode, mapTag, string(p.event.tag), "") + block := true + if p.event.mapping_style()&yaml_FLOW_MAPPING_STYLE != 0 { + block = false + n.Style |= FlowStyle + } + p.anchor(n, p.event.anchor) + p.expect(yaml_MAPPING_START_EVENT) + for p.peek() != yaml_MAPPING_END_EVENT { + k := p.parseChild(n) + if block && k.FootComment != "" { + // Must be a foot comment for the prior value when being dedented. + if len(n.Content) > 2 { + n.Content[len(n.Content)-3].FootComment = k.FootComment + k.FootComment = "" + } + } + v := p.parseChild(n) + if k.FootComment == "" && v.FootComment != "" { + k.FootComment = v.FootComment + v.FootComment = "" + } + if p.peek() == yaml_TAIL_COMMENT_EVENT { + if k.FootComment == "" { + k.FootComment = string(p.event.foot_comment) + } + p.expect(yaml_TAIL_COMMENT_EVENT) + } + } + n.LineComment = string(p.event.line_comment) + n.FootComment = string(p.event.foot_comment) + if n.Style&FlowStyle == 0 && n.FootComment != "" && len(n.Content) > 1 { + n.Content[len(n.Content)-2].FootComment = n.FootComment + n.FootComment = "" + } + p.expect(yaml_MAPPING_END_EVENT) + return n +} + +// ---------------------------------------------------------------------------- +// Decoder, unmarshals a node into a provided value. + +type decoder struct { + doc *Node + aliases map[*Node]bool + terrors []string + + stringMapType reflect.Type + generalMapType reflect.Type + + knownFields bool + uniqueKeys bool + decodeCount int + aliasCount int + aliasDepth int + + mergedFields map[interface{}]bool +} + +var ( + nodeType = reflect.TypeOf(Node{}) + durationType = reflect.TypeOf(time.Duration(0)) + stringMapType = reflect.TypeOf(map[string]interface{}{}) + generalMapType = reflect.TypeOf(map[interface{}]interface{}{}) + ifaceType = generalMapType.Elem() + timeType = reflect.TypeOf(time.Time{}) + ptrTimeType = reflect.TypeOf(&time.Time{}) +) + +func newDecoder() *decoder { + d := &decoder{ + stringMapType: stringMapType, + generalMapType: generalMapType, + uniqueKeys: true, + } + d.aliases = make(map[*Node]bool) + return d +} + +func (d *decoder) terror(n *Node, tag string, out reflect.Value) { + if n.Tag != "" { + tag = n.Tag + } + value := n.Value + if tag != seqTag && tag != mapTag { + if len(value) > 10 { + value = " `" + value[:7] + "...`" + } else { + value = " `" + value + "`" + } + } + d.terrors = append(d.terrors, fmt.Sprintf("line %d: cannot unmarshal %s%s into %s", n.Line, shortTag(tag), value, out.Type())) +} + +func (d *decoder) callUnmarshaler(n *Node, u Unmarshaler) (good bool) { + err := u.UnmarshalYAML(n) + if e, ok := err.(*TypeError); ok { + d.terrors = append(d.terrors, e.Errors...) + return false + } + if err != nil { + fail(err) + } + return true +} + +func (d *decoder) callObsoleteUnmarshaler(n *Node, u obsoleteUnmarshaler) (good bool) { + terrlen := len(d.terrors) + err := u.UnmarshalYAML(func(v interface{}) (err error) { + defer handleErr(&err) + d.unmarshal(n, reflect.ValueOf(v)) + if len(d.terrors) > terrlen { + issues := d.terrors[terrlen:] + d.terrors = d.terrors[:terrlen] + return &TypeError{issues} + } + return nil + }) + if e, ok := err.(*TypeError); ok { + d.terrors = append(d.terrors, e.Errors...) + return false + } + if err != nil { + fail(err) + } + return true +} + +// d.prepare initializes and dereferences pointers and calls UnmarshalYAML +// if a value is found to implement it. +// It returns the initialized and dereferenced out value, whether +// unmarshalling was already done by UnmarshalYAML, and if so whether +// its types unmarshalled appropriately. +// +// If n holds a null value, prepare returns before doing anything. +func (d *decoder) prepare(n *Node, out reflect.Value) (newout reflect.Value, unmarshaled, good bool) { + if n.ShortTag() == nullTag { + return out, false, false + } + again := true + for again { + again = false + if out.Kind() == reflect.Ptr { + if out.IsNil() { + out.Set(reflect.New(out.Type().Elem())) + } + out = out.Elem() + again = true + } + if out.CanAddr() { + outi := out.Addr().Interface() + if u, ok := outi.(Unmarshaler); ok { + good = d.callUnmarshaler(n, u) + return out, true, good + } + if u, ok := outi.(obsoleteUnmarshaler); ok { + good = d.callObsoleteUnmarshaler(n, u) + return out, true, good + } + } + } + return out, false, false +} + +func (d *decoder) fieldByIndex(n *Node, v reflect.Value, index []int) (field reflect.Value) { + if n.ShortTag() == nullTag { + return reflect.Value{} + } + for _, num := range index { + for { + if v.Kind() == reflect.Ptr { + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + v = v.Elem() + continue + } + break + } + v = v.Field(num) + } + return v +} + +const ( + // 400,000 decode operations is ~500kb of dense object declarations, or + // ~5kb of dense object declarations with 10000% alias expansion + alias_ratio_range_low = 400000 + + // 4,000,000 decode operations is ~5MB of dense object declarations, or + // ~4.5MB of dense object declarations with 10% alias expansion + alias_ratio_range_high = 4000000 + + // alias_ratio_range is the range over which we scale allowed alias ratios + alias_ratio_range = float64(alias_ratio_range_high - alias_ratio_range_low) +) + +func allowedAliasRatio(decodeCount int) float64 { + switch { + case decodeCount <= alias_ratio_range_low: + // allow 99% to come from alias expansion for small-to-medium documents + return 0.99 + case decodeCount >= alias_ratio_range_high: + // allow 10% to come from alias expansion for very large documents + return 0.10 + default: + // scale smoothly from 99% down to 10% over the range. + // this maps to 396,000 - 400,000 allowed alias-driven decodes over the range. + // 400,000 decode operations is ~100MB of allocations in worst-case scenarios (single-item maps). + return 0.99 - 0.89*(float64(decodeCount-alias_ratio_range_low)/alias_ratio_range) + } +} + +func (d *decoder) unmarshal(n *Node, out reflect.Value) (good bool) { + d.decodeCount++ + if d.aliasDepth > 0 { + d.aliasCount++ + } + if d.aliasCount > 100 && d.decodeCount > 1000 && float64(d.aliasCount)/float64(d.decodeCount) > allowedAliasRatio(d.decodeCount) { + failf("document contains excessive aliasing") + } + if out.Type() == nodeType { + out.Set(reflect.ValueOf(n).Elem()) + return true + } + switch n.Kind { + case DocumentNode: + return d.document(n, out) + case AliasNode: + return d.alias(n, out) + } + out, unmarshaled, good := d.prepare(n, out) + if unmarshaled { + return good + } + switch n.Kind { + case ScalarNode: + good = d.scalar(n, out) + case MappingNode: + good = d.mapping(n, out) + case SequenceNode: + good = d.sequence(n, out) + case 0: + if n.IsZero() { + return d.null(out) + } + fallthrough + default: + failf("cannot decode node with unknown kind %d", n.Kind) + } + return good +} + +func (d *decoder) document(n *Node, out reflect.Value) (good bool) { + if len(n.Content) == 1 { + d.doc = n + d.unmarshal(n.Content[0], out) + return true + } + return false +} + +func (d *decoder) alias(n *Node, out reflect.Value) (good bool) { + if d.aliases[n] { + // TODO this could actually be allowed in some circumstances. + failf("anchor '%s' value contains itself", n.Value) + } + d.aliases[n] = true + d.aliasDepth++ + good = d.unmarshal(n.Alias, out) + d.aliasDepth-- + delete(d.aliases, n) + return good +} + +var zeroValue reflect.Value + +func resetMap(out reflect.Value) { + for _, k := range out.MapKeys() { + out.SetMapIndex(k, zeroValue) + } +} + +func (d *decoder) null(out reflect.Value) bool { + if out.CanAddr() { + switch out.Kind() { + case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice: + out.Set(reflect.Zero(out.Type())) + return true + } + } + return false +} + +func (d *decoder) scalar(n *Node, out reflect.Value) bool { + var tag string + var resolved interface{} + if n.indicatedString() { + tag = strTag + resolved = n.Value + } else { + tag, resolved = resolve(n.Tag, n.Value) + if tag == binaryTag { + data, err := base64.StdEncoding.DecodeString(resolved.(string)) + if err != nil { + failf("!!binary value contains invalid base64 data") + } + resolved = string(data) + } + } + if resolved == nil { + return d.null(out) + } + if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() { + // We've resolved to exactly the type we want, so use that. + out.Set(resolvedv) + return true + } + // Perhaps we can use the value as a TextUnmarshaler to + // set its value. + if out.CanAddr() { + u, ok := out.Addr().Interface().(encoding.TextUnmarshaler) + if ok { + var text []byte + if tag == binaryTag { + text = []byte(resolved.(string)) + } else { + // We let any value be unmarshaled into TextUnmarshaler. + // That might be more lax than we'd like, but the + // TextUnmarshaler itself should bowl out any dubious values. + text = []byte(n.Value) + } + err := u.UnmarshalText(text) + if err != nil { + fail(err) + } + return true + } + } + switch out.Kind() { + case reflect.String: + if tag == binaryTag { + out.SetString(resolved.(string)) + return true + } + out.SetString(n.Value) + return true + case reflect.Interface: + out.Set(reflect.ValueOf(resolved)) + return true + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + // This used to work in v2, but it's very unfriendly. + isDuration := out.Type() == durationType + + switch resolved := resolved.(type) { + case int: + if !isDuration && !out.OverflowInt(int64(resolved)) { + out.SetInt(int64(resolved)) + return true + } + case int64: + if !isDuration && !out.OverflowInt(resolved) { + out.SetInt(resolved) + return true + } + case uint64: + if !isDuration && resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) { + out.SetInt(int64(resolved)) + return true + } + case float64: + if !isDuration && resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) { + out.SetInt(int64(resolved)) + return true + } + case string: + if out.Type() == durationType { + d, err := time.ParseDuration(resolved) + if err == nil { + out.SetInt(int64(d)) + return true + } + } + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + switch resolved := resolved.(type) { + case int: + if resolved >= 0 && !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + return true + } + case int64: + if resolved >= 0 && !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + return true + } + case uint64: + if !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + return true + } + case float64: + if resolved <= math.MaxUint64 && !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + return true + } + } + case reflect.Bool: + switch resolved := resolved.(type) { + case bool: + out.SetBool(resolved) + return true + case string: + // This offers some compatibility with the 1.1 spec (https://yaml.org/type/bool.html). + // It only works if explicitly attempting to unmarshal into a typed bool value. + switch resolved { + case "y", "Y", "yes", "Yes", "YES", "on", "On", "ON": + out.SetBool(true) + return true + case "n", "N", "no", "No", "NO", "off", "Off", "OFF": + out.SetBool(false) + return true + } + } + case reflect.Float32, reflect.Float64: + switch resolved := resolved.(type) { + case int: + out.SetFloat(float64(resolved)) + return true + case int64: + out.SetFloat(float64(resolved)) + return true + case uint64: + out.SetFloat(float64(resolved)) + return true + case float64: + out.SetFloat(resolved) + return true + } + case reflect.Struct: + if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() { + out.Set(resolvedv) + return true + } + case reflect.Ptr: + panic("yaml internal error: please report the issue") + } + d.terror(n, tag, out) + return false +} + +func settableValueOf(i interface{}) reflect.Value { + v := reflect.ValueOf(i) + sv := reflect.New(v.Type()).Elem() + sv.Set(v) + return sv +} + +func (d *decoder) sequence(n *Node, out reflect.Value) (good bool) { + l := len(n.Content) + + var iface reflect.Value + switch out.Kind() { + case reflect.Slice: + out.Set(reflect.MakeSlice(out.Type(), l, l)) + case reflect.Array: + if l != out.Len() { + failf("invalid array: want %d elements but got %d", out.Len(), l) + } + case reflect.Interface: + // No type hints. Will have to use a generic sequence. + iface = out + out = settableValueOf(make([]interface{}, l)) + default: + d.terror(n, seqTag, out) + return false + } + et := out.Type().Elem() + + j := 0 + for i := 0; i < l; i++ { + e := reflect.New(et).Elem() + if ok := d.unmarshal(n.Content[i], e); ok { + out.Index(j).Set(e) + j++ + } + } + if out.Kind() != reflect.Array { + out.Set(out.Slice(0, j)) + } + if iface.IsValid() { + iface.Set(out) + } + return true +} + +func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) { + l := len(n.Content) + if d.uniqueKeys { + nerrs := len(d.terrors) + for i := 0; i < l; i += 2 { + ni := n.Content[i] + for j := i + 2; j < l; j += 2 { + nj := n.Content[j] + if ni.Kind == nj.Kind && ni.Value == nj.Value { + d.terrors = append(d.terrors, fmt.Sprintf("line %d: mapping key %#v already defined at line %d", nj.Line, nj.Value, ni.Line)) + } + } + } + if len(d.terrors) > nerrs { + return false + } + } + switch out.Kind() { + case reflect.Struct: + return d.mappingStruct(n, out) + case reflect.Map: + // okay + case reflect.Interface: + iface := out + if isStringMap(n) { + out = reflect.MakeMap(d.stringMapType) + } else { + out = reflect.MakeMap(d.generalMapType) + } + iface.Set(out) + default: + d.terror(n, mapTag, out) + return false + } + + outt := out.Type() + kt := outt.Key() + et := outt.Elem() + + stringMapType := d.stringMapType + generalMapType := d.generalMapType + if outt.Elem() == ifaceType { + if outt.Key().Kind() == reflect.String { + d.stringMapType = outt + } else if outt.Key() == ifaceType { + d.generalMapType = outt + } + } + + mergedFields := d.mergedFields + d.mergedFields = nil + + var mergeNode *Node + + mapIsNew := false + if out.IsNil() { + out.Set(reflect.MakeMap(outt)) + mapIsNew = true + } + for i := 0; i < l; i += 2 { + if isMerge(n.Content[i]) { + mergeNode = n.Content[i+1] + continue + } + k := reflect.New(kt).Elem() + if d.unmarshal(n.Content[i], k) { + if mergedFields != nil { + ki := k.Interface() + if d.getPossiblyUnhashableKey(mergedFields, ki) { + continue + } + d.setPossiblyUnhashableKey(mergedFields, ki, true) + } + kkind := k.Kind() + if kkind == reflect.Interface { + kkind = k.Elem().Kind() + } + if kkind == reflect.Map || kkind == reflect.Slice { + failf("invalid map key: %#v", k.Interface()) + } + e := reflect.New(et).Elem() + if d.unmarshal(n.Content[i+1], e) || n.Content[i+1].ShortTag() == nullTag && (mapIsNew || !out.MapIndex(k).IsValid()) { + out.SetMapIndex(k, e) + } + } + } + + d.mergedFields = mergedFields + if mergeNode != nil { + d.merge(n, mergeNode, out) + } + + d.stringMapType = stringMapType + d.generalMapType = generalMapType + return true +} + +func isStringMap(n *Node) bool { + if n.Kind != MappingNode { + return false + } + l := len(n.Content) + for i := 0; i < l; i += 2 { + shortTag := n.Content[i].ShortTag() + if shortTag != strTag && shortTag != mergeTag { + return false + } + } + return true +} + +func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) { + sinfo, err := getStructInfo(out.Type()) + if err != nil { + panic(err) + } + + var inlineMap reflect.Value + var elemType reflect.Type + if sinfo.InlineMap != -1 { + inlineMap = out.Field(sinfo.InlineMap) + elemType = inlineMap.Type().Elem() + } + + for _, index := range sinfo.InlineUnmarshalers { + field := d.fieldByIndex(n, out, index) + d.prepare(n, field) + } + + mergedFields := d.mergedFields + d.mergedFields = nil + var mergeNode *Node + var doneFields []bool + if d.uniqueKeys { + doneFields = make([]bool, len(sinfo.FieldsList)) + } + name := settableValueOf("") + l := len(n.Content) + for i := 0; i < l; i += 2 { + ni := n.Content[i] + if isMerge(ni) { + mergeNode = n.Content[i+1] + continue + } + if !d.unmarshal(ni, name) { + continue + } + sname := name.String() + if mergedFields != nil { + if mergedFields[sname] { + continue + } + mergedFields[sname] = true + } + if info, ok := sinfo.FieldsMap[sname]; ok { + if d.uniqueKeys { + if doneFields[info.Id] { + d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s already set in type %s", ni.Line, name.String(), out.Type())) + continue + } + doneFields[info.Id] = true + } + var field reflect.Value + if info.Inline == nil { + field = out.Field(info.Num) + } else { + field = d.fieldByIndex(n, out, info.Inline) + } + d.unmarshal(n.Content[i+1], field) + } else if sinfo.InlineMap != -1 { + if inlineMap.IsNil() { + inlineMap.Set(reflect.MakeMap(inlineMap.Type())) + } + value := reflect.New(elemType).Elem() + d.unmarshal(n.Content[i+1], value) + inlineMap.SetMapIndex(name, value) + } else if d.knownFields { + d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s not found in type %s", ni.Line, name.String(), out.Type())) + } + } + + d.mergedFields = mergedFields + if mergeNode != nil { + d.merge(n, mergeNode, out) + } + return true +} + +func failWantMap() { + failf("map merge requires map or sequence of maps as the value") +} + +func (d *decoder) setPossiblyUnhashableKey(m map[interface{}]bool, key interface{}, value bool) { + defer func() { + if err := recover(); err != nil { + failf("%v", err) + } + }() + m[key] = value +} + +func (d *decoder) getPossiblyUnhashableKey(m map[interface{}]bool, key interface{}) bool { + defer func() { + if err := recover(); err != nil { + failf("%v", err) + } + }() + return m[key] +} + +func (d *decoder) merge(parent *Node, merge *Node, out reflect.Value) { + mergedFields := d.mergedFields + if mergedFields == nil { + d.mergedFields = make(map[interface{}]bool) + for i := 0; i < len(parent.Content); i += 2 { + k := reflect.New(ifaceType).Elem() + if d.unmarshal(parent.Content[i], k) { + d.setPossiblyUnhashableKey(d.mergedFields, k.Interface(), true) + } + } + } + + switch merge.Kind { + case MappingNode: + d.unmarshal(merge, out) + case AliasNode: + if merge.Alias != nil && merge.Alias.Kind != MappingNode { + failWantMap() + } + d.unmarshal(merge, out) + case SequenceNode: + for i := 0; i < len(merge.Content); i++ { + ni := merge.Content[i] + if ni.Kind == AliasNode { + if ni.Alias != nil && ni.Alias.Kind != MappingNode { + failWantMap() + } + } else if ni.Kind != MappingNode { + failWantMap() + } + d.unmarshal(ni, out) + } + default: + failWantMap() + } + + d.mergedFields = mergedFields +} + +func isMerge(n *Node) bool { + return n.Kind == ScalarNode && n.Value == "<<" && (n.Tag == "" || n.Tag == "!" || shortTag(n.Tag) == mergeTag) +} diff --git a/vendor/go.yaml.in/yaml/v3/emitterc.go b/vendor/go.yaml.in/yaml/v3/emitterc.go new file mode 100644 index 0000000000000..ab4e03ba726d0 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v3/emitterc.go @@ -0,0 +1,2054 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// Copyright (c) 2006-2010 Kirill Simonov +// +// 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. + +package yaml + +import ( + "bytes" + "fmt" +) + +// Flush the buffer if needed. +func flush(emitter *yaml_emitter_t) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) { + return yaml_emitter_flush(emitter) + } + return true +} + +// Put a character to the output buffer. +func put(emitter *yaml_emitter_t, value byte) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) && !yaml_emitter_flush(emitter) { + return false + } + emitter.buffer[emitter.buffer_pos] = value + emitter.buffer_pos++ + emitter.column++ + return true +} + +// Put a line break to the output buffer. +func put_break(emitter *yaml_emitter_t) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) && !yaml_emitter_flush(emitter) { + return false + } + switch emitter.line_break { + case yaml_CR_BREAK: + emitter.buffer[emitter.buffer_pos] = '\r' + emitter.buffer_pos += 1 + case yaml_LN_BREAK: + emitter.buffer[emitter.buffer_pos] = '\n' + emitter.buffer_pos += 1 + case yaml_CRLN_BREAK: + emitter.buffer[emitter.buffer_pos+0] = '\r' + emitter.buffer[emitter.buffer_pos+1] = '\n' + emitter.buffer_pos += 2 + default: + panic("unknown line break setting") + } + if emitter.column == 0 { + emitter.space_above = true + } + emitter.column = 0 + emitter.line++ + // [Go] Do this here and below and drop from everywhere else (see commented lines). + emitter.indention = true + return true +} + +// Copy a character from a string into buffer. +func write(emitter *yaml_emitter_t, s []byte, i *int) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) && !yaml_emitter_flush(emitter) { + return false + } + p := emitter.buffer_pos + w := width(s[*i]) + switch w { + case 4: + emitter.buffer[p+3] = s[*i+3] + fallthrough + case 3: + emitter.buffer[p+2] = s[*i+2] + fallthrough + case 2: + emitter.buffer[p+1] = s[*i+1] + fallthrough + case 1: + emitter.buffer[p+0] = s[*i+0] + default: + panic("unknown character width") + } + emitter.column++ + emitter.buffer_pos += w + *i += w + return true +} + +// Write a whole string into buffer. +func write_all(emitter *yaml_emitter_t, s []byte) bool { + for i := 0; i < len(s); { + if !write(emitter, s, &i) { + return false + } + } + return true +} + +// Copy a line break character from a string into buffer. +func write_break(emitter *yaml_emitter_t, s []byte, i *int) bool { + if s[*i] == '\n' { + if !put_break(emitter) { + return false + } + *i++ + } else { + if !write(emitter, s, i) { + return false + } + if emitter.column == 0 { + emitter.space_above = true + } + emitter.column = 0 + emitter.line++ + // [Go] Do this here and above and drop from everywhere else (see commented lines). + emitter.indention = true + } + return true +} + +// Set an emitter error and return false. +func yaml_emitter_set_emitter_error(emitter *yaml_emitter_t, problem string) bool { + emitter.error = yaml_EMITTER_ERROR + emitter.problem = problem + return false +} + +// Emit an event. +func yaml_emitter_emit(emitter *yaml_emitter_t, event *yaml_event_t) bool { + emitter.events = append(emitter.events, *event) + for !yaml_emitter_need_more_events(emitter) { + event := &emitter.events[emitter.events_head] + if !yaml_emitter_analyze_event(emitter, event) { + return false + } + if !yaml_emitter_state_machine(emitter, event) { + return false + } + yaml_event_delete(event) + emitter.events_head++ + } + return true +} + +// Check if we need to accumulate more events before emitting. +// +// We accumulate extra +// - 1 event for DOCUMENT-START +// - 2 events for SEQUENCE-START +// - 3 events for MAPPING-START +func yaml_emitter_need_more_events(emitter *yaml_emitter_t) bool { + if emitter.events_head == len(emitter.events) { + return true + } + var accumulate int + switch emitter.events[emitter.events_head].typ { + case yaml_DOCUMENT_START_EVENT: + accumulate = 1 + break + case yaml_SEQUENCE_START_EVENT: + accumulate = 2 + break + case yaml_MAPPING_START_EVENT: + accumulate = 3 + break + default: + return false + } + if len(emitter.events)-emitter.events_head > accumulate { + return false + } + var level int + for i := emitter.events_head; i < len(emitter.events); i++ { + switch emitter.events[i].typ { + case yaml_STREAM_START_EVENT, yaml_DOCUMENT_START_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT: + level++ + case yaml_STREAM_END_EVENT, yaml_DOCUMENT_END_EVENT, yaml_SEQUENCE_END_EVENT, yaml_MAPPING_END_EVENT: + level-- + } + if level == 0 { + return false + } + } + return true +} + +// Append a directive to the directives stack. +func yaml_emitter_append_tag_directive(emitter *yaml_emitter_t, value *yaml_tag_directive_t, allow_duplicates bool) bool { + for i := 0; i < len(emitter.tag_directives); i++ { + if bytes.Equal(value.handle, emitter.tag_directives[i].handle) { + if allow_duplicates { + return true + } + return yaml_emitter_set_emitter_error(emitter, "duplicate %TAG directive") + } + } + + // [Go] Do we actually need to copy this given garbage collection + // and the lack of deallocating destructors? + tag_copy := yaml_tag_directive_t{ + handle: make([]byte, len(value.handle)), + prefix: make([]byte, len(value.prefix)), + } + copy(tag_copy.handle, value.handle) + copy(tag_copy.prefix, value.prefix) + emitter.tag_directives = append(emitter.tag_directives, tag_copy) + return true +} + +// Increase the indentation level. +func yaml_emitter_increase_indent_compact(emitter *yaml_emitter_t, flow, indentless bool, compact_seq bool) bool { + emitter.indents = append(emitter.indents, emitter.indent) + if emitter.indent < 0 { + if flow { + emitter.indent = emitter.best_indent + } else { + emitter.indent = 0 + } + } else if !indentless { + // [Go] This was changed so that indentations are more regular. + if emitter.states[len(emitter.states)-1] == yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE { + // The first indent inside a sequence will just skip the "- " indicator. + emitter.indent += 2 + } else { + // Everything else aligns to the chosen indentation. + emitter.indent = emitter.best_indent * ((emitter.indent + emitter.best_indent) / emitter.best_indent) + if compact_seq { + // The value compact_seq passed in is almost always set to `false` when this function is called, + // except when we are dealing with sequence nodes. So this gets triggered to subtract 2 only when we + // are increasing the indent to account for sequence nodes, which will be correct because we need to + // subtract 2 to account for the - at the beginning of the sequence node. + emitter.indent = emitter.indent - 2 + } + } + } + return true +} + +// State dispatcher. +func yaml_emitter_state_machine(emitter *yaml_emitter_t, event *yaml_event_t) bool { + switch emitter.state { + default: + case yaml_EMIT_STREAM_START_STATE: + return yaml_emitter_emit_stream_start(emitter, event) + + case yaml_EMIT_FIRST_DOCUMENT_START_STATE: + return yaml_emitter_emit_document_start(emitter, event, true) + + case yaml_EMIT_DOCUMENT_START_STATE: + return yaml_emitter_emit_document_start(emitter, event, false) + + case yaml_EMIT_DOCUMENT_CONTENT_STATE: + return yaml_emitter_emit_document_content(emitter, event) + + case yaml_EMIT_DOCUMENT_END_STATE: + return yaml_emitter_emit_document_end(emitter, event) + + case yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE: + return yaml_emitter_emit_flow_sequence_item(emitter, event, true, false) + + case yaml_EMIT_FLOW_SEQUENCE_TRAIL_ITEM_STATE: + return yaml_emitter_emit_flow_sequence_item(emitter, event, false, true) + + case yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE: + return yaml_emitter_emit_flow_sequence_item(emitter, event, false, false) + + case yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE: + return yaml_emitter_emit_flow_mapping_key(emitter, event, true, false) + + case yaml_EMIT_FLOW_MAPPING_TRAIL_KEY_STATE: + return yaml_emitter_emit_flow_mapping_key(emitter, event, false, true) + + case yaml_EMIT_FLOW_MAPPING_KEY_STATE: + return yaml_emitter_emit_flow_mapping_key(emitter, event, false, false) + + case yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE: + return yaml_emitter_emit_flow_mapping_value(emitter, event, true) + + case yaml_EMIT_FLOW_MAPPING_VALUE_STATE: + return yaml_emitter_emit_flow_mapping_value(emitter, event, false) + + case yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE: + return yaml_emitter_emit_block_sequence_item(emitter, event, true) + + case yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE: + return yaml_emitter_emit_block_sequence_item(emitter, event, false) + + case yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE: + return yaml_emitter_emit_block_mapping_key(emitter, event, true) + + case yaml_EMIT_BLOCK_MAPPING_KEY_STATE: + return yaml_emitter_emit_block_mapping_key(emitter, event, false) + + case yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE: + return yaml_emitter_emit_block_mapping_value(emitter, event, true) + + case yaml_EMIT_BLOCK_MAPPING_VALUE_STATE: + return yaml_emitter_emit_block_mapping_value(emitter, event, false) + + case yaml_EMIT_END_STATE: + return yaml_emitter_set_emitter_error(emitter, "expected nothing after STREAM-END") + } + panic("invalid emitter state") +} + +// Expect STREAM-START. +func yaml_emitter_emit_stream_start(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if event.typ != yaml_STREAM_START_EVENT { + return yaml_emitter_set_emitter_error(emitter, "expected STREAM-START") + } + if emitter.encoding == yaml_ANY_ENCODING { + emitter.encoding = event.encoding + if emitter.encoding == yaml_ANY_ENCODING { + emitter.encoding = yaml_UTF8_ENCODING + } + } + if emitter.best_indent < 2 || emitter.best_indent > 9 { + emitter.best_indent = 2 + } + if emitter.best_width >= 0 && emitter.best_width <= emitter.best_indent*2 { + emitter.best_width = 80 + } + if emitter.best_width < 0 { + emitter.best_width = 1<<31 - 1 + } + if emitter.line_break == yaml_ANY_BREAK { + emitter.line_break = yaml_LN_BREAK + } + + emitter.indent = -1 + emitter.line = 0 + emitter.column = 0 + emitter.whitespace = true + emitter.indention = true + emitter.space_above = true + emitter.foot_indent = -1 + + if emitter.encoding != yaml_UTF8_ENCODING { + if !yaml_emitter_write_bom(emitter) { + return false + } + } + emitter.state = yaml_EMIT_FIRST_DOCUMENT_START_STATE + return true +} + +// Expect DOCUMENT-START or STREAM-END. +func yaml_emitter_emit_document_start(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + + if event.typ == yaml_DOCUMENT_START_EVENT { + + if event.version_directive != nil { + if !yaml_emitter_analyze_version_directive(emitter, event.version_directive) { + return false + } + } + + for i := 0; i < len(event.tag_directives); i++ { + tag_directive := &event.tag_directives[i] + if !yaml_emitter_analyze_tag_directive(emitter, tag_directive) { + return false + } + if !yaml_emitter_append_tag_directive(emitter, tag_directive, false) { + return false + } + } + + for i := 0; i < len(default_tag_directives); i++ { + tag_directive := &default_tag_directives[i] + if !yaml_emitter_append_tag_directive(emitter, tag_directive, true) { + return false + } + } + + implicit := event.implicit + if !first || emitter.canonical { + implicit = false + } + + if emitter.open_ended && (event.version_directive != nil || len(event.tag_directives) > 0) { + if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if event.version_directive != nil { + implicit = false + if !yaml_emitter_write_indicator(emitter, []byte("%YAML"), true, false, false) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte("1.1"), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if len(event.tag_directives) > 0 { + implicit = false + for i := 0; i < len(event.tag_directives); i++ { + tag_directive := &event.tag_directives[i] + if !yaml_emitter_write_indicator(emitter, []byte("%TAG"), true, false, false) { + return false + } + if !yaml_emitter_write_tag_handle(emitter, tag_directive.handle) { + return false + } + if !yaml_emitter_write_tag_content(emitter, tag_directive.prefix, true) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + } + + if yaml_emitter_check_empty_document(emitter) { + implicit = false + } + if !implicit { + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte("---"), true, false, false) { + return false + } + if emitter.canonical || true { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + } + + if len(emitter.head_comment) > 0 { + if !yaml_emitter_process_head_comment(emitter) { + return false + } + if !put_break(emitter) { + return false + } + } + + emitter.state = yaml_EMIT_DOCUMENT_CONTENT_STATE + return true + } + + if event.typ == yaml_STREAM_END_EVENT { + if emitter.open_ended { + if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_flush(emitter) { + return false + } + emitter.state = yaml_EMIT_END_STATE + return true + } + + return yaml_emitter_set_emitter_error(emitter, "expected DOCUMENT-START or STREAM-END") +} + +// yaml_emitter_increase_indent preserves the original signature and delegates to +// yaml_emitter_increase_indent_compact without compact-sequence indentation +func yaml_emitter_increase_indent(emitter *yaml_emitter_t, flow, indentless bool) bool { + return yaml_emitter_increase_indent_compact(emitter, flow, indentless, false) +} + +// yaml_emitter_process_line_comment preserves the original signature and delegates to +// yaml_emitter_process_line_comment_linebreak passing false for linebreak +func yaml_emitter_process_line_comment(emitter *yaml_emitter_t) bool { + return yaml_emitter_process_line_comment_linebreak(emitter, false) +} + +// Expect the root node. +func yaml_emitter_emit_document_content(emitter *yaml_emitter_t, event *yaml_event_t) bool { + emitter.states = append(emitter.states, yaml_EMIT_DOCUMENT_END_STATE) + + if !yaml_emitter_process_head_comment(emitter) { + return false + } + if !yaml_emitter_emit_node(emitter, event, true, false, false, false) { + return false + } + if !yaml_emitter_process_line_comment(emitter) { + return false + } + if !yaml_emitter_process_foot_comment(emitter) { + return false + } + return true +} + +// Expect DOCUMENT-END. +func yaml_emitter_emit_document_end(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if event.typ != yaml_DOCUMENT_END_EVENT { + return yaml_emitter_set_emitter_error(emitter, "expected DOCUMENT-END") + } + // [Go] Force document foot separation. + emitter.foot_indent = 0 + if !yaml_emitter_process_foot_comment(emitter) { + return false + } + emitter.foot_indent = -1 + if !yaml_emitter_write_indent(emitter) { + return false + } + if !event.implicit { + // [Go] Allocate the slice elsewhere. + if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_flush(emitter) { + return false + } + emitter.state = yaml_EMIT_DOCUMENT_START_STATE + emitter.tag_directives = emitter.tag_directives[:0] + return true +} + +// Expect a flow item node. +func yaml_emitter_emit_flow_sequence_item(emitter *yaml_emitter_t, event *yaml_event_t, first, trail bool) bool { + if first { + if !yaml_emitter_write_indicator(emitter, []byte{'['}, true, true, false) { + return false + } + if !yaml_emitter_increase_indent(emitter, true, false) { + return false + } + emitter.flow_level++ + } + + if event.typ == yaml_SEQUENCE_END_EVENT { + if emitter.canonical && !first && !trail { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + } + emitter.flow_level-- + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + if emitter.column == 0 || emitter.canonical && !first { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{']'}, false, false, false) { + return false + } + if !yaml_emitter_process_line_comment(emitter) { + return false + } + if !yaml_emitter_process_foot_comment(emitter) { + return false + } + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + + return true + } + + if !first && !trail { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + } + + if !yaml_emitter_process_head_comment(emitter) { + return false + } + if emitter.column == 0 { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if emitter.canonical || emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if len(emitter.line_comment)+len(emitter.foot_comment)+len(emitter.tail_comment) > 0 { + emitter.states = append(emitter.states, yaml_EMIT_FLOW_SEQUENCE_TRAIL_ITEM_STATE) + } else { + emitter.states = append(emitter.states, yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE) + } + if !yaml_emitter_emit_node(emitter, event, false, true, false, false) { + return false + } + if len(emitter.line_comment)+len(emitter.foot_comment)+len(emitter.tail_comment) > 0 { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + } + if !yaml_emitter_process_line_comment(emitter) { + return false + } + if !yaml_emitter_process_foot_comment(emitter) { + return false + } + return true +} + +// Expect a flow key node. +func yaml_emitter_emit_flow_mapping_key(emitter *yaml_emitter_t, event *yaml_event_t, first, trail bool) bool { + if first { + if !yaml_emitter_write_indicator(emitter, []byte{'{'}, true, true, false) { + return false + } + if !yaml_emitter_increase_indent(emitter, true, false) { + return false + } + emitter.flow_level++ + } + + if event.typ == yaml_MAPPING_END_EVENT { + if (emitter.canonical || len(emitter.head_comment)+len(emitter.foot_comment)+len(emitter.tail_comment) > 0) && !first && !trail { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + } + if !yaml_emitter_process_head_comment(emitter) { + return false + } + emitter.flow_level-- + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + if emitter.canonical && !first { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{'}'}, false, false, false) { + return false + } + if !yaml_emitter_process_line_comment(emitter) { + return false + } + if !yaml_emitter_process_foot_comment(emitter) { + return false + } + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true + } + + if !first && !trail { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + } + + if !yaml_emitter_process_head_comment(emitter) { + return false + } + + if emitter.column == 0 { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if emitter.canonical || emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if !emitter.canonical && yaml_emitter_check_simple_key(emitter) { + emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, true) + } + if !yaml_emitter_write_indicator(emitter, []byte{'?'}, true, false, false) { + return false + } + emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, false) +} + +// Expect a flow value node. +func yaml_emitter_emit_flow_mapping_value(emitter *yaml_emitter_t, event *yaml_event_t, simple bool) bool { + if simple { + if !yaml_emitter_write_indicator(emitter, []byte{':'}, false, false, false) { + return false + } + } else { + if emitter.canonical || emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{':'}, true, false, false) { + return false + } + } + if len(emitter.line_comment)+len(emitter.foot_comment)+len(emitter.tail_comment) > 0 { + emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_TRAIL_KEY_STATE) + } else { + emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_KEY_STATE) + } + if !yaml_emitter_emit_node(emitter, event, false, false, true, false) { + return false + } + if len(emitter.line_comment)+len(emitter.foot_comment)+len(emitter.tail_comment) > 0 { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + } + if !yaml_emitter_process_line_comment(emitter) { + return false + } + if !yaml_emitter_process_foot_comment(emitter) { + return false + } + return true +} + +// Expect a block item node. +func yaml_emitter_emit_block_sequence_item(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + if first { + // emitter.mapping context tells us if we are currently in a mapping context. + // emiiter.column tells us which column we are in in the yaml output. 0 is the first char of the column. + // emitter.indentation tells us if the last character was an indentation character. + // emitter.compact_sequence_indent tells us if '- ' is considered part of the indentation for sequence elements. + // So, `seq` means that we are in a mapping context, and we are either at the first char of the column or + // the last character was not an indentation character, and we consider '- ' part of the indentation + // for sequence elements. + seq := emitter.mapping_context && (emitter.column == 0 || !emitter.indention) && + emitter.compact_sequence_indent + if !yaml_emitter_increase_indent_compact(emitter, false, false, seq) { + return false + } + } + if event.typ == yaml_SEQUENCE_END_EVENT { + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true + } + if !yaml_emitter_process_head_comment(emitter) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte{'-'}, true, false, true) { + return false + } + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE) + if !yaml_emitter_emit_node(emitter, event, false, true, false, false) { + return false + } + if !yaml_emitter_process_line_comment(emitter) { + return false + } + if !yaml_emitter_process_foot_comment(emitter) { + return false + } + return true +} + +// Expect a block key node. +func yaml_emitter_emit_block_mapping_key(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + if first { + if !yaml_emitter_increase_indent(emitter, false, false) { + return false + } + } + if !yaml_emitter_process_head_comment(emitter) { + return false + } + if event.typ == yaml_MAPPING_END_EVENT { + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true + } + if !yaml_emitter_write_indent(emitter) { + return false + } + if len(emitter.line_comment) > 0 { + // [Go] A line comment was provided for the key. That's unusual as the + // scanner associates line comments with the value. Either way, + // save the line comment and render it appropriately later. + emitter.key_line_comment = emitter.line_comment + emitter.line_comment = nil + } + if yaml_emitter_check_simple_key(emitter) { + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, true) + } + if !yaml_emitter_write_indicator(emitter, []byte{'?'}, true, false, true) { + return false + } + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, false) +} + +// Expect a block value node. +func yaml_emitter_emit_block_mapping_value(emitter *yaml_emitter_t, event *yaml_event_t, simple bool) bool { + if simple { + if !yaml_emitter_write_indicator(emitter, []byte{':'}, false, false, false) { + return false + } + } else { + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte{':'}, true, false, true) { + return false + } + } + if len(emitter.key_line_comment) > 0 { + // [Go] Line comments are generally associated with the value, but when there's + // no value on the same line as a mapping key they end up attached to the + // key itself. + if event.typ == yaml_SCALAR_EVENT { + if len(emitter.line_comment) == 0 { + // A scalar is coming and it has no line comments by itself yet, + // so just let it handle the line comment as usual. If it has a + // line comment, we can't have both so the one from the key is lost. + emitter.line_comment = emitter.key_line_comment + emitter.key_line_comment = nil + } + } else if event.sequence_style() != yaml_FLOW_SEQUENCE_STYLE && (event.typ == yaml_MAPPING_START_EVENT || event.typ == yaml_SEQUENCE_START_EVENT) { + // An indented block follows, so write the comment right now. + emitter.line_comment, emitter.key_line_comment = emitter.key_line_comment, emitter.line_comment + if !yaml_emitter_process_line_comment(emitter) { + return false + } + emitter.line_comment, emitter.key_line_comment = emitter.key_line_comment, emitter.line_comment + } + } + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_KEY_STATE) + if !yaml_emitter_emit_node(emitter, event, false, false, true, false) { + return false + } + if !yaml_emitter_process_line_comment(emitter) { + return false + } + if !yaml_emitter_process_foot_comment(emitter) { + return false + } + return true +} + +func yaml_emitter_silent_nil_event(emitter *yaml_emitter_t, event *yaml_event_t) bool { + return event.typ == yaml_SCALAR_EVENT && event.implicit && !emitter.canonical && len(emitter.scalar_data.value) == 0 +} + +// Expect a node. +func yaml_emitter_emit_node(emitter *yaml_emitter_t, event *yaml_event_t, + root bool, sequence bool, mapping bool, simple_key bool) bool { + + emitter.root_context = root + emitter.sequence_context = sequence + emitter.mapping_context = mapping + emitter.simple_key_context = simple_key + + switch event.typ { + case yaml_ALIAS_EVENT: + return yaml_emitter_emit_alias(emitter, event) + case yaml_SCALAR_EVENT: + return yaml_emitter_emit_scalar(emitter, event) + case yaml_SEQUENCE_START_EVENT: + return yaml_emitter_emit_sequence_start(emitter, event) + case yaml_MAPPING_START_EVENT: + return yaml_emitter_emit_mapping_start(emitter, event) + default: + return yaml_emitter_set_emitter_error(emitter, + fmt.Sprintf("expected SCALAR, SEQUENCE-START, MAPPING-START, or ALIAS, but got %v", event.typ)) + } +} + +// Expect ALIAS. +func yaml_emitter_emit_alias(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_process_anchor(emitter) { + return false + } + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true +} + +// Expect SCALAR. +func yaml_emitter_emit_scalar(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_select_scalar_style(emitter, event) { + return false + } + if !yaml_emitter_process_anchor(emitter) { + return false + } + if !yaml_emitter_process_tag(emitter) { + return false + } + if !yaml_emitter_increase_indent(emitter, true, false) { + return false + } + if !yaml_emitter_process_scalar(emitter) { + return false + } + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true +} + +// Expect SEQUENCE-START. +func yaml_emitter_emit_sequence_start(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_process_anchor(emitter) { + return false + } + if !yaml_emitter_process_tag(emitter) { + return false + } + if emitter.flow_level > 0 || emitter.canonical || event.sequence_style() == yaml_FLOW_SEQUENCE_STYLE || + yaml_emitter_check_empty_sequence(emitter) { + emitter.state = yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE + } else { + emitter.state = yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE + } + return true +} + +// Expect MAPPING-START. +func yaml_emitter_emit_mapping_start(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_process_anchor(emitter) { + return false + } + if !yaml_emitter_process_tag(emitter) { + return false + } + if emitter.flow_level > 0 || emitter.canonical || event.mapping_style() == yaml_FLOW_MAPPING_STYLE || + yaml_emitter_check_empty_mapping(emitter) { + emitter.state = yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE + } else { + emitter.state = yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE + } + return true +} + +// Check if the document content is an empty scalar. +func yaml_emitter_check_empty_document(emitter *yaml_emitter_t) bool { + return false // [Go] Huh? +} + +// Check if the next events represent an empty sequence. +func yaml_emitter_check_empty_sequence(emitter *yaml_emitter_t) bool { + if len(emitter.events)-emitter.events_head < 2 { + return false + } + return emitter.events[emitter.events_head].typ == yaml_SEQUENCE_START_EVENT && + emitter.events[emitter.events_head+1].typ == yaml_SEQUENCE_END_EVENT +} + +// Check if the next events represent an empty mapping. +func yaml_emitter_check_empty_mapping(emitter *yaml_emitter_t) bool { + if len(emitter.events)-emitter.events_head < 2 { + return false + } + return emitter.events[emitter.events_head].typ == yaml_MAPPING_START_EVENT && + emitter.events[emitter.events_head+1].typ == yaml_MAPPING_END_EVENT +} + +// Check if the next node can be expressed as a simple key. +func yaml_emitter_check_simple_key(emitter *yaml_emitter_t) bool { + length := 0 + switch emitter.events[emitter.events_head].typ { + case yaml_ALIAS_EVENT: + length += len(emitter.anchor_data.anchor) + case yaml_SCALAR_EVENT: + if emitter.scalar_data.multiline { + return false + } + length += len(emitter.anchor_data.anchor) + + len(emitter.tag_data.handle) + + len(emitter.tag_data.suffix) + + len(emitter.scalar_data.value) + case yaml_SEQUENCE_START_EVENT: + if !yaml_emitter_check_empty_sequence(emitter) { + return false + } + length += len(emitter.anchor_data.anchor) + + len(emitter.tag_data.handle) + + len(emitter.tag_data.suffix) + case yaml_MAPPING_START_EVENT: + if !yaml_emitter_check_empty_mapping(emitter) { + return false + } + length += len(emitter.anchor_data.anchor) + + len(emitter.tag_data.handle) + + len(emitter.tag_data.suffix) + default: + return false + } + return length <= 128 +} + +// Determine an acceptable scalar style. +func yaml_emitter_select_scalar_style(emitter *yaml_emitter_t, event *yaml_event_t) bool { + + no_tag := len(emitter.tag_data.handle) == 0 && len(emitter.tag_data.suffix) == 0 + if no_tag && !event.implicit && !event.quoted_implicit { + return yaml_emitter_set_emitter_error(emitter, "neither tag nor implicit flags are specified") + } + + style := event.scalar_style() + if style == yaml_ANY_SCALAR_STYLE { + style = yaml_PLAIN_SCALAR_STYLE + } + if emitter.canonical { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + if emitter.simple_key_context && emitter.scalar_data.multiline { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + + if style == yaml_PLAIN_SCALAR_STYLE { + if emitter.flow_level > 0 && !emitter.scalar_data.flow_plain_allowed || + emitter.flow_level == 0 && !emitter.scalar_data.block_plain_allowed { + style = yaml_SINGLE_QUOTED_SCALAR_STYLE + } + if len(emitter.scalar_data.value) == 0 && (emitter.flow_level > 0 || emitter.simple_key_context) { + style = yaml_SINGLE_QUOTED_SCALAR_STYLE + } + if no_tag && !event.implicit { + style = yaml_SINGLE_QUOTED_SCALAR_STYLE + } + } + if style == yaml_SINGLE_QUOTED_SCALAR_STYLE { + if !emitter.scalar_data.single_quoted_allowed { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + } + if style == yaml_LITERAL_SCALAR_STYLE || style == yaml_FOLDED_SCALAR_STYLE { + if !emitter.scalar_data.block_allowed || emitter.flow_level > 0 || emitter.simple_key_context { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + } + + if no_tag && !event.quoted_implicit && style != yaml_PLAIN_SCALAR_STYLE { + emitter.tag_data.handle = []byte{'!'} + } + emitter.scalar_data.style = style + return true +} + +// Write an anchor. +func yaml_emitter_process_anchor(emitter *yaml_emitter_t) bool { + if emitter.anchor_data.anchor == nil { + return true + } + c := []byte{'&'} + if emitter.anchor_data.alias { + c[0] = '*' + } + if !yaml_emitter_write_indicator(emitter, c, true, false, false) { + return false + } + return yaml_emitter_write_anchor(emitter, emitter.anchor_data.anchor) +} + +// Write a tag. +func yaml_emitter_process_tag(emitter *yaml_emitter_t) bool { + if len(emitter.tag_data.handle) == 0 && len(emitter.tag_data.suffix) == 0 { + return true + } + if len(emitter.tag_data.handle) > 0 { + if !yaml_emitter_write_tag_handle(emitter, emitter.tag_data.handle) { + return false + } + if len(emitter.tag_data.suffix) > 0 { + if !yaml_emitter_write_tag_content(emitter, emitter.tag_data.suffix, false) { + return false + } + } + } else { + // [Go] Allocate these slices elsewhere. + if !yaml_emitter_write_indicator(emitter, []byte("!<"), true, false, false) { + return false + } + if !yaml_emitter_write_tag_content(emitter, emitter.tag_data.suffix, false) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte{'>'}, false, false, false) { + return false + } + } + return true +} + +// Write a scalar. +func yaml_emitter_process_scalar(emitter *yaml_emitter_t) bool { + switch emitter.scalar_data.style { + case yaml_PLAIN_SCALAR_STYLE: + return yaml_emitter_write_plain_scalar(emitter, emitter.scalar_data.value, !emitter.simple_key_context) + + case yaml_SINGLE_QUOTED_SCALAR_STYLE: + return yaml_emitter_write_single_quoted_scalar(emitter, emitter.scalar_data.value, !emitter.simple_key_context) + + case yaml_DOUBLE_QUOTED_SCALAR_STYLE: + return yaml_emitter_write_double_quoted_scalar(emitter, emitter.scalar_data.value, !emitter.simple_key_context) + + case yaml_LITERAL_SCALAR_STYLE: + return yaml_emitter_write_literal_scalar(emitter, emitter.scalar_data.value) + + case yaml_FOLDED_SCALAR_STYLE: + return yaml_emitter_write_folded_scalar(emitter, emitter.scalar_data.value) + } + panic("unknown scalar style") +} + +// Write a head comment. +func yaml_emitter_process_head_comment(emitter *yaml_emitter_t) bool { + if len(emitter.tail_comment) > 0 { + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_comment(emitter, emitter.tail_comment) { + return false + } + emitter.tail_comment = emitter.tail_comment[:0] + emitter.foot_indent = emitter.indent + if emitter.foot_indent < 0 { + emitter.foot_indent = 0 + } + } + + if len(emitter.head_comment) == 0 { + return true + } + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_comment(emitter, emitter.head_comment) { + return false + } + emitter.head_comment = emitter.head_comment[:0] + return true +} + +// Write an line comment. +func yaml_emitter_process_line_comment_linebreak(emitter *yaml_emitter_t, linebreak bool) bool { + if len(emitter.line_comment) == 0 { + // The next 3 lines are needed to resolve an issue with leading newlines + // See https://github.com/go-yaml/yaml/issues/755 + // When linebreak is set to true, put_break will be called and will add + // the needed newline. + if linebreak && !put_break(emitter) { + return false + } + return true + } + if !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + if !yaml_emitter_write_comment(emitter, emitter.line_comment) { + return false + } + emitter.line_comment = emitter.line_comment[:0] + return true +} + +// Write a foot comment. +func yaml_emitter_process_foot_comment(emitter *yaml_emitter_t) bool { + if len(emitter.foot_comment) == 0 { + return true + } + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_comment(emitter, emitter.foot_comment) { + return false + } + emitter.foot_comment = emitter.foot_comment[:0] + emitter.foot_indent = emitter.indent + if emitter.foot_indent < 0 { + emitter.foot_indent = 0 + } + return true +} + +// Check if a %YAML directive is valid. +func yaml_emitter_analyze_version_directive(emitter *yaml_emitter_t, version_directive *yaml_version_directive_t) bool { + if version_directive.major != 1 || version_directive.minor != 1 { + return yaml_emitter_set_emitter_error(emitter, "incompatible %YAML directive") + } + return true +} + +// Check if a %TAG directive is valid. +func yaml_emitter_analyze_tag_directive(emitter *yaml_emitter_t, tag_directive *yaml_tag_directive_t) bool { + handle := tag_directive.handle + prefix := tag_directive.prefix + if len(handle) == 0 { + return yaml_emitter_set_emitter_error(emitter, "tag handle must not be empty") + } + if handle[0] != '!' { + return yaml_emitter_set_emitter_error(emitter, "tag handle must start with '!'") + } + if handle[len(handle)-1] != '!' { + return yaml_emitter_set_emitter_error(emitter, "tag handle must end with '!'") + } + for i := 1; i < len(handle)-1; i += width(handle[i]) { + if !is_alpha(handle, i) { + return yaml_emitter_set_emitter_error(emitter, "tag handle must contain alphanumerical characters only") + } + } + if len(prefix) == 0 { + return yaml_emitter_set_emitter_error(emitter, "tag prefix must not be empty") + } + return true +} + +// Check if an anchor is valid. +func yaml_emitter_analyze_anchor(emitter *yaml_emitter_t, anchor []byte, alias bool) bool { + if len(anchor) == 0 { + problem := "anchor value must not be empty" + if alias { + problem = "alias value must not be empty" + } + return yaml_emitter_set_emitter_error(emitter, problem) + } + for i := 0; i < len(anchor); i += width(anchor[i]) { + if !is_alpha(anchor, i) { + problem := "anchor value must contain alphanumerical characters only" + if alias { + problem = "alias value must contain alphanumerical characters only" + } + return yaml_emitter_set_emitter_error(emitter, problem) + } + } + emitter.anchor_data.anchor = anchor + emitter.anchor_data.alias = alias + return true +} + +// Check if a tag is valid. +func yaml_emitter_analyze_tag(emitter *yaml_emitter_t, tag []byte) bool { + if len(tag) == 0 { + return yaml_emitter_set_emitter_error(emitter, "tag value must not be empty") + } + for i := 0; i < len(emitter.tag_directives); i++ { + tag_directive := &emitter.tag_directives[i] + if bytes.HasPrefix(tag, tag_directive.prefix) { + emitter.tag_data.handle = tag_directive.handle + emitter.tag_data.suffix = tag[len(tag_directive.prefix):] + return true + } + } + emitter.tag_data.suffix = tag + return true +} + +// Check if a scalar is valid. +func yaml_emitter_analyze_scalar(emitter *yaml_emitter_t, value []byte) bool { + var ( + block_indicators = false + flow_indicators = false + line_breaks = false + special_characters = false + tab_characters = false + + leading_space = false + leading_break = false + trailing_space = false + trailing_break = false + break_space = false + space_break = false + + preceded_by_whitespace = false + followed_by_whitespace = false + previous_space = false + previous_break = false + ) + + emitter.scalar_data.value = value + + if len(value) == 0 { + emitter.scalar_data.multiline = false + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = true + emitter.scalar_data.single_quoted_allowed = true + emitter.scalar_data.block_allowed = false + return true + } + + if len(value) >= 3 && ((value[0] == '-' && value[1] == '-' && value[2] == '-') || (value[0] == '.' && value[1] == '.' && value[2] == '.')) { + block_indicators = true + flow_indicators = true + } + + preceded_by_whitespace = true + for i, w := 0, 0; i < len(value); i += w { + w = width(value[i]) + followed_by_whitespace = i+w >= len(value) || is_blank(value, i+w) + + if i == 0 { + switch value[i] { + case '#', ',', '[', ']', '{', '}', '&', '*', '!', '|', '>', '\'', '"', '%', '@', '`': + flow_indicators = true + block_indicators = true + case '?', ':': + flow_indicators = true + if followed_by_whitespace { + block_indicators = true + } + case '-': + if followed_by_whitespace { + flow_indicators = true + block_indicators = true + } + } + } else { + switch value[i] { + case ',', '?', '[', ']', '{', '}': + flow_indicators = true + case ':': + flow_indicators = true + if followed_by_whitespace { + block_indicators = true + } + case '#': + if preceded_by_whitespace { + flow_indicators = true + block_indicators = true + } + } + } + + if value[i] == '\t' { + tab_characters = true + } else if !is_printable(value, i) || !is_ascii(value, i) && !emitter.unicode { + special_characters = true + } + if is_space(value, i) { + if i == 0 { + leading_space = true + } + if i+width(value[i]) == len(value) { + trailing_space = true + } + if previous_break { + break_space = true + } + previous_space = true + previous_break = false + } else if is_break(value, i) { + line_breaks = true + if i == 0 { + leading_break = true + } + if i+width(value[i]) == len(value) { + trailing_break = true + } + if previous_space { + space_break = true + } + previous_space = false + previous_break = true + } else { + previous_space = false + previous_break = false + } + + // [Go]: Why 'z'? Couldn't be the end of the string as that's the loop condition. + preceded_by_whitespace = is_blankz(value, i) + } + + emitter.scalar_data.multiline = line_breaks + emitter.scalar_data.flow_plain_allowed = true + emitter.scalar_data.block_plain_allowed = true + emitter.scalar_data.single_quoted_allowed = true + emitter.scalar_data.block_allowed = true + + if leading_space || leading_break || trailing_space || trailing_break { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + } + if trailing_space { + emitter.scalar_data.block_allowed = false + } + if break_space { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + emitter.scalar_data.single_quoted_allowed = false + } + if space_break || tab_characters || special_characters { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + emitter.scalar_data.single_quoted_allowed = false + } + if space_break || special_characters { + emitter.scalar_data.block_allowed = false + } + if line_breaks { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + } + if flow_indicators { + emitter.scalar_data.flow_plain_allowed = false + } + if block_indicators { + emitter.scalar_data.block_plain_allowed = false + } + return true +} + +// Check if the event data is valid. +func yaml_emitter_analyze_event(emitter *yaml_emitter_t, event *yaml_event_t) bool { + + emitter.anchor_data.anchor = nil + emitter.tag_data.handle = nil + emitter.tag_data.suffix = nil + emitter.scalar_data.value = nil + + if len(event.head_comment) > 0 { + emitter.head_comment = event.head_comment + } + if len(event.line_comment) > 0 { + emitter.line_comment = event.line_comment + } + if len(event.foot_comment) > 0 { + emitter.foot_comment = event.foot_comment + } + if len(event.tail_comment) > 0 { + emitter.tail_comment = event.tail_comment + } + + switch event.typ { + case yaml_ALIAS_EVENT: + if !yaml_emitter_analyze_anchor(emitter, event.anchor, true) { + return false + } + + case yaml_SCALAR_EVENT: + if len(event.anchor) > 0 { + if !yaml_emitter_analyze_anchor(emitter, event.anchor, false) { + return false + } + } + if len(event.tag) > 0 && (emitter.canonical || (!event.implicit && !event.quoted_implicit)) { + if !yaml_emitter_analyze_tag(emitter, event.tag) { + return false + } + } + if !yaml_emitter_analyze_scalar(emitter, event.value) { + return false + } + + case yaml_SEQUENCE_START_EVENT: + if len(event.anchor) > 0 { + if !yaml_emitter_analyze_anchor(emitter, event.anchor, false) { + return false + } + } + if len(event.tag) > 0 && (emitter.canonical || !event.implicit) { + if !yaml_emitter_analyze_tag(emitter, event.tag) { + return false + } + } + + case yaml_MAPPING_START_EVENT: + if len(event.anchor) > 0 { + if !yaml_emitter_analyze_anchor(emitter, event.anchor, false) { + return false + } + } + if len(event.tag) > 0 && (emitter.canonical || !event.implicit) { + if !yaml_emitter_analyze_tag(emitter, event.tag) { + return false + } + } + } + return true +} + +// Write the BOM character. +func yaml_emitter_write_bom(emitter *yaml_emitter_t) bool { + if !flush(emitter) { + return false + } + pos := emitter.buffer_pos + emitter.buffer[pos+0] = '\xEF' + emitter.buffer[pos+1] = '\xBB' + emitter.buffer[pos+2] = '\xBF' + emitter.buffer_pos += 3 + return true +} + +func yaml_emitter_write_indent(emitter *yaml_emitter_t) bool { + indent := emitter.indent + if indent < 0 { + indent = 0 + } + if !emitter.indention || emitter.column > indent || (emitter.column == indent && !emitter.whitespace) { + if !put_break(emitter) { + return false + } + } + if emitter.foot_indent == indent { + if !put_break(emitter) { + return false + } + } + for emitter.column < indent { + if !put(emitter, ' ') { + return false + } + } + emitter.whitespace = true + //emitter.indention = true + emitter.space_above = false + emitter.foot_indent = -1 + return true +} + +func yaml_emitter_write_indicator(emitter *yaml_emitter_t, indicator []byte, need_whitespace, is_whitespace, is_indention bool) bool { + if need_whitespace && !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + if !write_all(emitter, indicator) { + return false + } + emitter.whitespace = is_whitespace + emitter.indention = (emitter.indention && is_indention) + emitter.open_ended = false + return true +} + +func yaml_emitter_write_anchor(emitter *yaml_emitter_t, value []byte) bool { + if !write_all(emitter, value) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_tag_handle(emitter *yaml_emitter_t, value []byte) bool { + if !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + if !write_all(emitter, value) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_tag_content(emitter *yaml_emitter_t, value []byte, need_whitespace bool) bool { + if need_whitespace && !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + for i := 0; i < len(value); { + var must_write bool + switch value[i] { + case ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '_', '.', '~', '*', '\'', '(', ')', '[', ']': + must_write = true + default: + must_write = is_alpha(value, i) + } + if must_write { + if !write(emitter, value, &i) { + return false + } + } else { + w := width(value[i]) + for k := 0; k < w; k++ { + octet := value[i] + i++ + if !put(emitter, '%') { + return false + } + + c := octet >> 4 + if c < 10 { + c += '0' + } else { + c += 'A' - 10 + } + if !put(emitter, c) { + return false + } + + c = octet & 0x0f + if c < 10 { + c += '0' + } else { + c += 'A' - 10 + } + if !put(emitter, c) { + return false + } + } + } + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_plain_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool { + if len(value) > 0 && !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + + spaces := false + breaks := false + for i := 0; i < len(value); { + if is_space(value, i) { + if allow_breaks && !spaces && emitter.column > emitter.best_width && !is_space(value, i+1) { + if !yaml_emitter_write_indent(emitter) { + return false + } + i += width(value[i]) + } else { + if !write(emitter, value, &i) { + return false + } + } + spaces = true + } else if is_break(value, i) { + if !breaks && value[i] == '\n' { + if !put_break(emitter) { + return false + } + } + if !write_break(emitter, value, &i) { + return false + } + //emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !write(emitter, value, &i) { + return false + } + emitter.indention = false + spaces = false + breaks = false + } + } + + if len(value) > 0 { + emitter.whitespace = false + } + emitter.indention = false + if emitter.root_context { + emitter.open_ended = true + } + + return true +} + +func yaml_emitter_write_single_quoted_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool { + + if !yaml_emitter_write_indicator(emitter, []byte{'\''}, true, false, false) { + return false + } + + spaces := false + breaks := false + for i := 0; i < len(value); { + if is_space(value, i) { + if allow_breaks && !spaces && emitter.column > emitter.best_width && i > 0 && i < len(value)-1 && !is_space(value, i+1) { + if !yaml_emitter_write_indent(emitter) { + return false + } + i += width(value[i]) + } else { + if !write(emitter, value, &i) { + return false + } + } + spaces = true + } else if is_break(value, i) { + if !breaks && value[i] == '\n' { + if !put_break(emitter) { + return false + } + } + if !write_break(emitter, value, &i) { + return false + } + //emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if value[i] == '\'' { + if !put(emitter, '\'') { + return false + } + } + if !write(emitter, value, &i) { + return false + } + emitter.indention = false + spaces = false + breaks = false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{'\''}, false, false, false) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_double_quoted_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool { + spaces := false + if !yaml_emitter_write_indicator(emitter, []byte{'"'}, true, false, false) { + return false + } + + for i := 0; i < len(value); { + if !is_printable(value, i) || (!emitter.unicode && !is_ascii(value, i)) || + is_bom(value, i) || is_break(value, i) || + value[i] == '"' || value[i] == '\\' { + + octet := value[i] + + var w int + var v rune + switch { + case octet&0x80 == 0x00: + w, v = 1, rune(octet&0x7F) + case octet&0xE0 == 0xC0: + w, v = 2, rune(octet&0x1F) + case octet&0xF0 == 0xE0: + w, v = 3, rune(octet&0x0F) + case octet&0xF8 == 0xF0: + w, v = 4, rune(octet&0x07) + } + for k := 1; k < w; k++ { + octet = value[i+k] + v = (v << 6) + (rune(octet) & 0x3F) + } + i += w + + if !put(emitter, '\\') { + return false + } + + var ok bool + switch v { + case 0x00: + ok = put(emitter, '0') + case 0x07: + ok = put(emitter, 'a') + case 0x08: + ok = put(emitter, 'b') + case 0x09: + ok = put(emitter, 't') + case 0x0A: + ok = put(emitter, 'n') + case 0x0b: + ok = put(emitter, 'v') + case 0x0c: + ok = put(emitter, 'f') + case 0x0d: + ok = put(emitter, 'r') + case 0x1b: + ok = put(emitter, 'e') + case 0x22: + ok = put(emitter, '"') + case 0x5c: + ok = put(emitter, '\\') + case 0x85: + ok = put(emitter, 'N') + case 0xA0: + ok = put(emitter, '_') + case 0x2028: + ok = put(emitter, 'L') + case 0x2029: + ok = put(emitter, 'P') + default: + if v <= 0xFF { + ok = put(emitter, 'x') + w = 2 + } else if v <= 0xFFFF { + ok = put(emitter, 'u') + w = 4 + } else { + ok = put(emitter, 'U') + w = 8 + } + for k := (w - 1) * 4; ok && k >= 0; k -= 4 { + digit := byte((v >> uint(k)) & 0x0F) + if digit < 10 { + ok = put(emitter, digit+'0') + } else { + ok = put(emitter, digit+'A'-10) + } + } + } + if !ok { + return false + } + spaces = false + } else if is_space(value, i) { + if allow_breaks && !spaces && emitter.column > emitter.best_width && i > 0 && i < len(value)-1 { + if !yaml_emitter_write_indent(emitter) { + return false + } + if is_space(value, i+1) { + if !put(emitter, '\\') { + return false + } + } + i += width(value[i]) + } else if !write(emitter, value, &i) { + return false + } + spaces = true + } else { + if !write(emitter, value, &i) { + return false + } + spaces = false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{'"'}, false, false, false) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_block_scalar_hints(emitter *yaml_emitter_t, value []byte) bool { + if is_space(value, 0) || is_break(value, 0) { + indent_hint := []byte{'0' + byte(emitter.best_indent)} + if !yaml_emitter_write_indicator(emitter, indent_hint, false, false, false) { + return false + } + } + + emitter.open_ended = false + + var chomp_hint [1]byte + if len(value) == 0 { + chomp_hint[0] = '-' + } else { + i := len(value) - 1 + for value[i]&0xC0 == 0x80 { + i-- + } + if !is_break(value, i) { + chomp_hint[0] = '-' + } else if i == 0 { + chomp_hint[0] = '+' + emitter.open_ended = true + } else { + i-- + for value[i]&0xC0 == 0x80 { + i-- + } + if is_break(value, i) { + chomp_hint[0] = '+' + emitter.open_ended = true + } + } + } + if chomp_hint[0] != 0 { + if !yaml_emitter_write_indicator(emitter, chomp_hint[:], false, false, false) { + return false + } + } + return true +} + +func yaml_emitter_write_literal_scalar(emitter *yaml_emitter_t, value []byte) bool { + if !yaml_emitter_write_indicator(emitter, []byte{'|'}, true, false, false) { + return false + } + if !yaml_emitter_write_block_scalar_hints(emitter, value) { + return false + } + if !yaml_emitter_process_line_comment_linebreak(emitter, true) { + return false + } + //emitter.indention = true + emitter.whitespace = true + breaks := true + for i := 0; i < len(value); { + if is_break(value, i) { + if !write_break(emitter, value, &i) { + return false + } + //emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !write(emitter, value, &i) { + return false + } + emitter.indention = false + breaks = false + } + } + + return true +} + +func yaml_emitter_write_folded_scalar(emitter *yaml_emitter_t, value []byte) bool { + if !yaml_emitter_write_indicator(emitter, []byte{'>'}, true, false, false) { + return false + } + if !yaml_emitter_write_block_scalar_hints(emitter, value) { + return false + } + if !yaml_emitter_process_line_comment_linebreak(emitter, true) { + return false + } + + //emitter.indention = true + emitter.whitespace = true + + breaks := true + leading_spaces := true + for i := 0; i < len(value); { + if is_break(value, i) { + if !breaks && !leading_spaces && value[i] == '\n' { + k := 0 + for is_break(value, k) { + k += width(value[k]) + } + if !is_blankz(value, k) { + if !put_break(emitter) { + return false + } + } + } + if !write_break(emitter, value, &i) { + return false + } + //emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + leading_spaces = is_blank(value, i) + } + if !breaks && is_space(value, i) && !is_space(value, i+1) && emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + i += width(value[i]) + } else { + if !write(emitter, value, &i) { + return false + } + } + emitter.indention = false + breaks = false + } + } + return true +} + +func yaml_emitter_write_comment(emitter *yaml_emitter_t, comment []byte) bool { + breaks := false + pound := false + for i := 0; i < len(comment); { + if is_break(comment, i) { + if !write_break(emitter, comment, &i) { + return false + } + //emitter.indention = true + breaks = true + pound = false + } else { + if breaks && !yaml_emitter_write_indent(emitter) { + return false + } + if !pound { + if comment[i] != '#' && (!put(emitter, '#') || !put(emitter, ' ')) { + return false + } + pound = true + } + if !write(emitter, comment, &i) { + return false + } + emitter.indention = false + breaks = false + } + } + if !breaks && !put_break(emitter) { + return false + } + + emitter.whitespace = true + //emitter.indention = true + return true +} diff --git a/vendor/go.yaml.in/yaml/v3/encode.go b/vendor/go.yaml.in/yaml/v3/encode.go new file mode 100644 index 0000000000000..de9e72a3e638d --- /dev/null +++ b/vendor/go.yaml.in/yaml/v3/encode.go @@ -0,0 +1,577 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// +// 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 yaml + +import ( + "encoding" + "fmt" + "io" + "reflect" + "regexp" + "sort" + "strconv" + "strings" + "time" + "unicode/utf8" +) + +type encoder struct { + emitter yaml_emitter_t + event yaml_event_t + out []byte + flow bool + indent int + doneInit bool +} + +func newEncoder() *encoder { + e := &encoder{} + yaml_emitter_initialize(&e.emitter) + yaml_emitter_set_output_string(&e.emitter, &e.out) + yaml_emitter_set_unicode(&e.emitter, true) + return e +} + +func newEncoderWithWriter(w io.Writer) *encoder { + e := &encoder{} + yaml_emitter_initialize(&e.emitter) + yaml_emitter_set_output_writer(&e.emitter, w) + yaml_emitter_set_unicode(&e.emitter, true) + return e +} + +func (e *encoder) init() { + if e.doneInit { + return + } + if e.indent == 0 { + e.indent = 4 + } + e.emitter.best_indent = e.indent + yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING) + e.emit() + e.doneInit = true +} + +func (e *encoder) finish() { + e.emitter.open_ended = false + yaml_stream_end_event_initialize(&e.event) + e.emit() +} + +func (e *encoder) destroy() { + yaml_emitter_delete(&e.emitter) +} + +func (e *encoder) emit() { + // This will internally delete the e.event value. + e.must(yaml_emitter_emit(&e.emitter, &e.event)) +} + +func (e *encoder) must(ok bool) { + if !ok { + msg := e.emitter.problem + if msg == "" { + msg = "unknown problem generating YAML content" + } + failf("%s", msg) + } +} + +func (e *encoder) marshalDoc(tag string, in reflect.Value) { + e.init() + var node *Node + if in.IsValid() { + node, _ = in.Interface().(*Node) + } + if node != nil && node.Kind == DocumentNode { + e.nodev(in) + } else { + yaml_document_start_event_initialize(&e.event, nil, nil, true) + e.emit() + e.marshal(tag, in) + yaml_document_end_event_initialize(&e.event, true) + e.emit() + } +} + +func (e *encoder) marshal(tag string, in reflect.Value) { + tag = shortTag(tag) + if !in.IsValid() || in.Kind() == reflect.Ptr && in.IsNil() { + e.nilv() + return + } + iface := in.Interface() + switch value := iface.(type) { + case *Node: + e.nodev(in) + return + case Node: + if !in.CanAddr() { + var n = reflect.New(in.Type()).Elem() + n.Set(in) + in = n + } + e.nodev(in.Addr()) + return + case time.Time: + e.timev(tag, in) + return + case *time.Time: + e.timev(tag, in.Elem()) + return + case time.Duration: + e.stringv(tag, reflect.ValueOf(value.String())) + return + case Marshaler: + v, err := value.MarshalYAML() + if err != nil { + fail(err) + } + if v == nil { + e.nilv() + return + } + e.marshal(tag, reflect.ValueOf(v)) + return + case encoding.TextMarshaler: + text, err := value.MarshalText() + if err != nil { + fail(err) + } + in = reflect.ValueOf(string(text)) + case nil: + e.nilv() + return + } + switch in.Kind() { + case reflect.Interface: + e.marshal(tag, in.Elem()) + case reflect.Map: + e.mapv(tag, in) + case reflect.Ptr: + e.marshal(tag, in.Elem()) + case reflect.Struct: + e.structv(tag, in) + case reflect.Slice, reflect.Array: + e.slicev(tag, in) + case reflect.String: + e.stringv(tag, in) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + e.intv(tag, in) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + e.uintv(tag, in) + case reflect.Float32, reflect.Float64: + e.floatv(tag, in) + case reflect.Bool: + e.boolv(tag, in) + default: + panic("cannot marshal type: " + in.Type().String()) + } +} + +func (e *encoder) mapv(tag string, in reflect.Value) { + e.mappingv(tag, func() { + keys := keyList(in.MapKeys()) + sort.Sort(keys) + for _, k := range keys { + e.marshal("", k) + e.marshal("", in.MapIndex(k)) + } + }) +} + +func (e *encoder) fieldByIndex(v reflect.Value, index []int) (field reflect.Value) { + for _, num := range index { + for { + if v.Kind() == reflect.Ptr { + if v.IsNil() { + return reflect.Value{} + } + v = v.Elem() + continue + } + break + } + v = v.Field(num) + } + return v +} + +func (e *encoder) structv(tag string, in reflect.Value) { + sinfo, err := getStructInfo(in.Type()) + if err != nil { + panic(err) + } + e.mappingv(tag, func() { + for _, info := range sinfo.FieldsList { + var value reflect.Value + if info.Inline == nil { + value = in.Field(info.Num) + } else { + value = e.fieldByIndex(in, info.Inline) + if !value.IsValid() { + continue + } + } + if info.OmitEmpty && isZero(value) { + continue + } + e.marshal("", reflect.ValueOf(info.Key)) + e.flow = info.Flow + e.marshal("", value) + } + if sinfo.InlineMap >= 0 { + m := in.Field(sinfo.InlineMap) + if m.Len() > 0 { + e.flow = false + keys := keyList(m.MapKeys()) + sort.Sort(keys) + for _, k := range keys { + if _, found := sinfo.FieldsMap[k.String()]; found { + panic(fmt.Sprintf("cannot have key %q in inlined map: conflicts with struct field", k.String())) + } + e.marshal("", k) + e.flow = false + e.marshal("", m.MapIndex(k)) + } + } + } + }) +} + +func (e *encoder) mappingv(tag string, f func()) { + implicit := tag == "" + style := yaml_BLOCK_MAPPING_STYLE + if e.flow { + e.flow = false + style = yaml_FLOW_MAPPING_STYLE + } + yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style) + e.emit() + f() + yaml_mapping_end_event_initialize(&e.event) + e.emit() +} + +func (e *encoder) slicev(tag string, in reflect.Value) { + implicit := tag == "" + style := yaml_BLOCK_SEQUENCE_STYLE + if e.flow { + e.flow = false + style = yaml_FLOW_SEQUENCE_STYLE + } + e.must(yaml_sequence_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)) + e.emit() + n := in.Len() + for i := 0; i < n; i++ { + e.marshal("", in.Index(i)) + } + e.must(yaml_sequence_end_event_initialize(&e.event)) + e.emit() +} + +// isBase60 returns whether s is in base 60 notation as defined in YAML 1.1. +// +// The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported +// in YAML 1.2 and by this package, but these should be marshalled quoted for +// the time being for compatibility with other parsers. +func isBase60Float(s string) (result bool) { + // Fast path. + if s == "" { + return false + } + c := s[0] + if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 { + return false + } + // Do the full match. + return base60float.MatchString(s) +} + +// From http://yaml.org/type/float.html, except the regular expression there +// is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix. +var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`) + +// isOldBool returns whether s is bool notation as defined in YAML 1.1. +// +// We continue to force strings that YAML 1.1 would interpret as booleans to be +// rendered as quotes strings so that the marshalled output valid for YAML 1.1 +// parsing. +func isOldBool(s string) (result bool) { + switch s { + case "y", "Y", "yes", "Yes", "YES", "on", "On", "ON", + "n", "N", "no", "No", "NO", "off", "Off", "OFF": + return true + default: + return false + } +} + +func (e *encoder) stringv(tag string, in reflect.Value) { + var style yaml_scalar_style_t + s := in.String() + canUsePlain := true + switch { + case !utf8.ValidString(s): + if tag == binaryTag { + failf("explicitly tagged !!binary data must be base64-encoded") + } + if tag != "" { + failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag)) + } + // It can't be encoded directly as YAML so use a binary tag + // and encode it as base64. + tag = binaryTag + s = encodeBase64(s) + case tag == "": + // Check to see if it would resolve to a specific + // tag when encoded unquoted. If it doesn't, + // there's no need to quote it. + rtag, _ := resolve("", s) + canUsePlain = rtag == strTag && !(isBase60Float(s) || isOldBool(s)) + } + // Note: it's possible for user code to emit invalid YAML + // if they explicitly specify a tag and a string containing + // text that's incompatible with that tag. + switch { + case strings.Contains(s, "\n"): + if e.flow { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } else { + style = yaml_LITERAL_SCALAR_STYLE + } + case canUsePlain: + style = yaml_PLAIN_SCALAR_STYLE + default: + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + e.emitScalar(s, "", tag, style, nil, nil, nil, nil) +} + +func (e *encoder) boolv(tag string, in reflect.Value) { + var s string + if in.Bool() { + s = "true" + } else { + s = "false" + } + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil) +} + +func (e *encoder) intv(tag string, in reflect.Value) { + s := strconv.FormatInt(in.Int(), 10) + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil) +} + +func (e *encoder) uintv(tag string, in reflect.Value) { + s := strconv.FormatUint(in.Uint(), 10) + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil) +} + +func (e *encoder) timev(tag string, in reflect.Value) { + t := in.Interface().(time.Time) + s := t.Format(time.RFC3339Nano) + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil) +} + +func (e *encoder) floatv(tag string, in reflect.Value) { + // Issue #352: When formatting, use the precision of the underlying value + precision := 64 + if in.Kind() == reflect.Float32 { + precision = 32 + } + + s := strconv.FormatFloat(in.Float(), 'g', -1, precision) + switch s { + case "+Inf": + s = ".inf" + case "-Inf": + s = "-.inf" + case "NaN": + s = ".nan" + } + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil) +} + +func (e *encoder) nilv() { + e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil) +} + +func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t, head, line, foot, tail []byte) { + // TODO Kill this function. Replace all initialize calls by their underlining Go literals. + implicit := tag == "" + if !implicit { + tag = longTag(tag) + } + e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style)) + e.event.head_comment = head + e.event.line_comment = line + e.event.foot_comment = foot + e.event.tail_comment = tail + e.emit() +} + +func (e *encoder) nodev(in reflect.Value) { + e.node(in.Interface().(*Node), "") +} + +func (e *encoder) node(node *Node, tail string) { + // Zero nodes behave as nil. + if node.Kind == 0 && node.IsZero() { + e.nilv() + return + } + + // If the tag was not explicitly requested, and dropping it won't change the + // implicit tag of the value, don't include it in the presentation. + var tag = node.Tag + var stag = shortTag(tag) + var forceQuoting bool + if tag != "" && node.Style&TaggedStyle == 0 { + if node.Kind == ScalarNode { + if stag == strTag && node.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0 { + tag = "" + } else { + rtag, _ := resolve("", node.Value) + if rtag == stag { + tag = "" + } else if stag == strTag { + tag = "" + forceQuoting = true + } + } + } else { + var rtag string + switch node.Kind { + case MappingNode: + rtag = mapTag + case SequenceNode: + rtag = seqTag + } + if rtag == stag { + tag = "" + } + } + } + + switch node.Kind { + case DocumentNode: + yaml_document_start_event_initialize(&e.event, nil, nil, true) + e.event.head_comment = []byte(node.HeadComment) + e.emit() + for _, node := range node.Content { + e.node(node, "") + } + yaml_document_end_event_initialize(&e.event, true) + e.event.foot_comment = []byte(node.FootComment) + e.emit() + + case SequenceNode: + style := yaml_BLOCK_SEQUENCE_STYLE + if node.Style&FlowStyle != 0 { + style = yaml_FLOW_SEQUENCE_STYLE + } + e.must(yaml_sequence_start_event_initialize(&e.event, []byte(node.Anchor), []byte(longTag(tag)), tag == "", style)) + e.event.head_comment = []byte(node.HeadComment) + e.emit() + for _, node := range node.Content { + e.node(node, "") + } + e.must(yaml_sequence_end_event_initialize(&e.event)) + e.event.line_comment = []byte(node.LineComment) + e.event.foot_comment = []byte(node.FootComment) + e.emit() + + case MappingNode: + style := yaml_BLOCK_MAPPING_STYLE + if node.Style&FlowStyle != 0 { + style = yaml_FLOW_MAPPING_STYLE + } + yaml_mapping_start_event_initialize(&e.event, []byte(node.Anchor), []byte(longTag(tag)), tag == "", style) + e.event.tail_comment = []byte(tail) + e.event.head_comment = []byte(node.HeadComment) + e.emit() + + // The tail logic below moves the foot comment of prior keys to the following key, + // since the value for each key may be a nested structure and the foot needs to be + // processed only the entirety of the value is streamed. The last tail is processed + // with the mapping end event. + var tail string + for i := 0; i+1 < len(node.Content); i += 2 { + k := node.Content[i] + foot := k.FootComment + if foot != "" { + kopy := *k + kopy.FootComment = "" + k = &kopy + } + e.node(k, tail) + tail = foot + + v := node.Content[i+1] + e.node(v, "") + } + + yaml_mapping_end_event_initialize(&e.event) + e.event.tail_comment = []byte(tail) + e.event.line_comment = []byte(node.LineComment) + e.event.foot_comment = []byte(node.FootComment) + e.emit() + + case AliasNode: + yaml_alias_event_initialize(&e.event, []byte(node.Value)) + e.event.head_comment = []byte(node.HeadComment) + e.event.line_comment = []byte(node.LineComment) + e.event.foot_comment = []byte(node.FootComment) + e.emit() + + case ScalarNode: + value := node.Value + if !utf8.ValidString(value) { + if stag == binaryTag { + failf("explicitly tagged !!binary data must be base64-encoded") + } + if stag != "" { + failf("cannot marshal invalid UTF-8 data as %s", stag) + } + // It can't be encoded directly as YAML so use a binary tag + // and encode it as base64. + tag = binaryTag + value = encodeBase64(value) + } + + style := yaml_PLAIN_SCALAR_STYLE + switch { + case node.Style&DoubleQuotedStyle != 0: + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + case node.Style&SingleQuotedStyle != 0: + style = yaml_SINGLE_QUOTED_SCALAR_STYLE + case node.Style&LiteralStyle != 0: + style = yaml_LITERAL_SCALAR_STYLE + case node.Style&FoldedStyle != 0: + style = yaml_FOLDED_SCALAR_STYLE + case strings.Contains(value, "\n"): + style = yaml_LITERAL_SCALAR_STYLE + case forceQuoting: + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + + e.emitScalar(value, node.Anchor, tag, style, []byte(node.HeadComment), []byte(node.LineComment), []byte(node.FootComment), []byte(tail)) + default: + failf("cannot encode node with unknown kind %d", node.Kind) + } +} diff --git a/vendor/go.yaml.in/yaml/v3/parserc.go b/vendor/go.yaml.in/yaml/v3/parserc.go new file mode 100644 index 0000000000000..25fe823637abc --- /dev/null +++ b/vendor/go.yaml.in/yaml/v3/parserc.go @@ -0,0 +1,1274 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// Copyright (c) 2006-2010 Kirill Simonov +// +// 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. + +package yaml + +import ( + "bytes" +) + +// The parser implements the following grammar: +// +// stream ::= STREAM-START implicit_document? explicit_document* STREAM-END +// implicit_document ::= block_node DOCUMENT-END* +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// block_node_or_indentless_sequence ::= +// ALIAS +// | properties (block_content | indentless_block_sequence)? +// | block_content +// | indentless_block_sequence +// block_node ::= ALIAS +// | properties block_content? +// | block_content +// flow_node ::= ALIAS +// | properties flow_content? +// | flow_content +// properties ::= TAG ANCHOR? | ANCHOR TAG? +// block_content ::= block_collection | flow_collection | SCALAR +// flow_content ::= flow_collection | SCALAR +// block_collection ::= block_sequence | block_mapping +// flow_collection ::= flow_sequence | flow_mapping +// block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END +// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ +// block_mapping ::= BLOCK-MAPPING_START +// ((KEY block_node_or_indentless_sequence?)? +// (VALUE block_node_or_indentless_sequence?)?)* +// BLOCK-END +// flow_sequence ::= FLOW-SEQUENCE-START +// (flow_sequence_entry FLOW-ENTRY)* +// flow_sequence_entry? +// FLOW-SEQUENCE-END +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// flow_mapping ::= FLOW-MAPPING-START +// (flow_mapping_entry FLOW-ENTRY)* +// flow_mapping_entry? +// FLOW-MAPPING-END +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + +// Peek the next token in the token queue. +func peek_token(parser *yaml_parser_t) *yaml_token_t { + if parser.token_available || yaml_parser_fetch_more_tokens(parser) { + token := &parser.tokens[parser.tokens_head] + yaml_parser_unfold_comments(parser, token) + return token + } + return nil +} + +// yaml_parser_unfold_comments walks through the comments queue and joins all +// comments behind the position of the provided token into the respective +// top-level comment slices in the parser. +func yaml_parser_unfold_comments(parser *yaml_parser_t, token *yaml_token_t) { + for parser.comments_head < len(parser.comments) && token.start_mark.index >= parser.comments[parser.comments_head].token_mark.index { + comment := &parser.comments[parser.comments_head] + if len(comment.head) > 0 { + if token.typ == yaml_BLOCK_END_TOKEN { + // No heads on ends, so keep comment.head for a follow up token. + break + } + if len(parser.head_comment) > 0 { + parser.head_comment = append(parser.head_comment, '\n') + } + parser.head_comment = append(parser.head_comment, comment.head...) + } + if len(comment.foot) > 0 { + if len(parser.foot_comment) > 0 { + parser.foot_comment = append(parser.foot_comment, '\n') + } + parser.foot_comment = append(parser.foot_comment, comment.foot...) + } + if len(comment.line) > 0 { + if len(parser.line_comment) > 0 { + parser.line_comment = append(parser.line_comment, '\n') + } + parser.line_comment = append(parser.line_comment, comment.line...) + } + *comment = yaml_comment_t{} + parser.comments_head++ + } +} + +// Remove the next token from the queue (must be called after peek_token). +func skip_token(parser *yaml_parser_t) { + parser.token_available = false + parser.tokens_parsed++ + parser.stream_end_produced = parser.tokens[parser.tokens_head].typ == yaml_STREAM_END_TOKEN + parser.tokens_head++ +} + +// Get the next event. +func yaml_parser_parse(parser *yaml_parser_t, event *yaml_event_t) bool { + // Erase the event object. + *event = yaml_event_t{} + + // No events after the end of the stream or error. + if parser.stream_end_produced || parser.error != yaml_NO_ERROR || parser.state == yaml_PARSE_END_STATE { + return true + } + + // Generate the next event. + return yaml_parser_state_machine(parser, event) +} + +// Set parser error. +func yaml_parser_set_parser_error(parser *yaml_parser_t, problem string, problem_mark yaml_mark_t) bool { + parser.error = yaml_PARSER_ERROR + parser.problem = problem + parser.problem_mark = problem_mark + return false +} + +func yaml_parser_set_parser_error_context(parser *yaml_parser_t, context string, context_mark yaml_mark_t, problem string, problem_mark yaml_mark_t) bool { + parser.error = yaml_PARSER_ERROR + parser.context = context + parser.context_mark = context_mark + parser.problem = problem + parser.problem_mark = problem_mark + return false +} + +// State dispatcher. +func yaml_parser_state_machine(parser *yaml_parser_t, event *yaml_event_t) bool { + //trace("yaml_parser_state_machine", "state:", parser.state.String()) + + switch parser.state { + case yaml_PARSE_STREAM_START_STATE: + return yaml_parser_parse_stream_start(parser, event) + + case yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE: + return yaml_parser_parse_document_start(parser, event, true) + + case yaml_PARSE_DOCUMENT_START_STATE: + return yaml_parser_parse_document_start(parser, event, false) + + case yaml_PARSE_DOCUMENT_CONTENT_STATE: + return yaml_parser_parse_document_content(parser, event) + + case yaml_PARSE_DOCUMENT_END_STATE: + return yaml_parser_parse_document_end(parser, event) + + case yaml_PARSE_BLOCK_NODE_STATE: + return yaml_parser_parse_node(parser, event, true, false) + + case yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE: + return yaml_parser_parse_node(parser, event, true, true) + + case yaml_PARSE_FLOW_NODE_STATE: + return yaml_parser_parse_node(parser, event, false, false) + + case yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE: + return yaml_parser_parse_block_sequence_entry(parser, event, true) + + case yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE: + return yaml_parser_parse_block_sequence_entry(parser, event, false) + + case yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE: + return yaml_parser_parse_indentless_sequence_entry(parser, event) + + case yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE: + return yaml_parser_parse_block_mapping_key(parser, event, true) + + case yaml_PARSE_BLOCK_MAPPING_KEY_STATE: + return yaml_parser_parse_block_mapping_key(parser, event, false) + + case yaml_PARSE_BLOCK_MAPPING_VALUE_STATE: + return yaml_parser_parse_block_mapping_value(parser, event) + + case yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE: + return yaml_parser_parse_flow_sequence_entry(parser, event, true) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE: + return yaml_parser_parse_flow_sequence_entry(parser, event, false) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE: + return yaml_parser_parse_flow_sequence_entry_mapping_key(parser, event) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE: + return yaml_parser_parse_flow_sequence_entry_mapping_value(parser, event) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE: + return yaml_parser_parse_flow_sequence_entry_mapping_end(parser, event) + + case yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE: + return yaml_parser_parse_flow_mapping_key(parser, event, true) + + case yaml_PARSE_FLOW_MAPPING_KEY_STATE: + return yaml_parser_parse_flow_mapping_key(parser, event, false) + + case yaml_PARSE_FLOW_MAPPING_VALUE_STATE: + return yaml_parser_parse_flow_mapping_value(parser, event, false) + + case yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE: + return yaml_parser_parse_flow_mapping_value(parser, event, true) + + default: + panic("invalid parser state") + } +} + +// Parse the production: +// stream ::= STREAM-START implicit_document? explicit_document* STREAM-END +// +// ************ +func yaml_parser_parse_stream_start(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_STREAM_START_TOKEN { + return yaml_parser_set_parser_error(parser, "did not find expected ", token.start_mark) + } + parser.state = yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE + *event = yaml_event_t{ + typ: yaml_STREAM_START_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + encoding: token.encoding, + } + skip_token(parser) + return true +} + +// Parse the productions: +// implicit_document ::= block_node DOCUMENT-END* +// +// * +// +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// +// ************************* +func yaml_parser_parse_document_start(parser *yaml_parser_t, event *yaml_event_t, implicit bool) bool { + + token := peek_token(parser) + if token == nil { + return false + } + + // Parse extra document end indicators. + if !implicit { + for token.typ == yaml_DOCUMENT_END_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + } + + if implicit && token.typ != yaml_VERSION_DIRECTIVE_TOKEN && + token.typ != yaml_TAG_DIRECTIVE_TOKEN && + token.typ != yaml_DOCUMENT_START_TOKEN && + token.typ != yaml_STREAM_END_TOKEN { + // Parse an implicit document. + if !yaml_parser_process_directives(parser, nil, nil) { + return false + } + parser.states = append(parser.states, yaml_PARSE_DOCUMENT_END_STATE) + parser.state = yaml_PARSE_BLOCK_NODE_STATE + + var head_comment []byte + if len(parser.head_comment) > 0 { + // [Go] Scan the header comment backwards, and if an empty line is found, break + // the header so the part before the last empty line goes into the + // document header, while the bottom of it goes into a follow up event. + for i := len(parser.head_comment) - 1; i > 0; i-- { + if parser.head_comment[i] == '\n' { + if i == len(parser.head_comment)-1 { + head_comment = parser.head_comment[:i] + parser.head_comment = parser.head_comment[i+1:] + break + } else if parser.head_comment[i-1] == '\n' { + head_comment = parser.head_comment[:i-1] + parser.head_comment = parser.head_comment[i+1:] + break + } + } + } + } + + *event = yaml_event_t{ + typ: yaml_DOCUMENT_START_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + + head_comment: head_comment, + } + + } else if token.typ != yaml_STREAM_END_TOKEN { + // Parse an explicit document. + var version_directive *yaml_version_directive_t + var tag_directives []yaml_tag_directive_t + start_mark := token.start_mark + if !yaml_parser_process_directives(parser, &version_directive, &tag_directives) { + return false + } + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_DOCUMENT_START_TOKEN { + yaml_parser_set_parser_error(parser, + "did not find expected ", token.start_mark) + return false + } + parser.states = append(parser.states, yaml_PARSE_DOCUMENT_END_STATE) + parser.state = yaml_PARSE_DOCUMENT_CONTENT_STATE + end_mark := token.end_mark + + *event = yaml_event_t{ + typ: yaml_DOCUMENT_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + version_directive: version_directive, + tag_directives: tag_directives, + implicit: false, + } + skip_token(parser) + + } else { + // Parse the stream end. + parser.state = yaml_PARSE_END_STATE + *event = yaml_event_t{ + typ: yaml_STREAM_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + skip_token(parser) + } + + return true +} + +// Parse the productions: +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// +// *********** +func yaml_parser_parse_document_content(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_VERSION_DIRECTIVE_TOKEN || + token.typ == yaml_TAG_DIRECTIVE_TOKEN || + token.typ == yaml_DOCUMENT_START_TOKEN || + token.typ == yaml_DOCUMENT_END_TOKEN || + token.typ == yaml_STREAM_END_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + return yaml_parser_process_empty_scalar(parser, event, + token.start_mark) + } + return yaml_parser_parse_node(parser, event, true, false) +} + +// Parse the productions: +// implicit_document ::= block_node DOCUMENT-END* +// +// ************* +// +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +func yaml_parser_parse_document_end(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + + start_mark := token.start_mark + end_mark := token.start_mark + + implicit := true + if token.typ == yaml_DOCUMENT_END_TOKEN { + end_mark = token.end_mark + skip_token(parser) + implicit = false + } + + parser.tag_directives = parser.tag_directives[:0] + + parser.state = yaml_PARSE_DOCUMENT_START_STATE + *event = yaml_event_t{ + typ: yaml_DOCUMENT_END_EVENT, + start_mark: start_mark, + end_mark: end_mark, + implicit: implicit, + } + yaml_parser_set_event_comments(parser, event) + if len(event.head_comment) > 0 && len(event.foot_comment) == 0 { + event.foot_comment = event.head_comment + event.head_comment = nil + } + return true +} + +func yaml_parser_set_event_comments(parser *yaml_parser_t, event *yaml_event_t) { + event.head_comment = parser.head_comment + event.line_comment = parser.line_comment + event.foot_comment = parser.foot_comment + parser.head_comment = nil + parser.line_comment = nil + parser.foot_comment = nil + parser.tail_comment = nil + parser.stem_comment = nil +} + +// Parse the productions: +// block_node_or_indentless_sequence ::= +// +// ALIAS +// ***** +// | properties (block_content | indentless_block_sequence)? +// ********** * +// | block_content | indentless_block_sequence +// * +// +// block_node ::= ALIAS +// +// ***** +// | properties block_content? +// ********** * +// | block_content +// * +// +// flow_node ::= ALIAS +// +// ***** +// | properties flow_content? +// ********** * +// | flow_content +// * +// +// properties ::= TAG ANCHOR? | ANCHOR TAG? +// +// ************************* +// +// block_content ::= block_collection | flow_collection | SCALAR +// +// ****** +// +// flow_content ::= flow_collection | SCALAR +// +// ****** +func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, indentless_sequence bool) bool { + //defer trace("yaml_parser_parse_node", "block:", block, "indentless_sequence:", indentless_sequence)() + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_ALIAS_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + *event = yaml_event_t{ + typ: yaml_ALIAS_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + anchor: token.value, + } + yaml_parser_set_event_comments(parser, event) + skip_token(parser) + return true + } + + start_mark := token.start_mark + end_mark := token.start_mark + + var tag_token bool + var tag_handle, tag_suffix, anchor []byte + var tag_mark yaml_mark_t + if token.typ == yaml_ANCHOR_TOKEN { + anchor = token.value + start_mark = token.start_mark + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_TAG_TOKEN { + tag_token = true + tag_handle = token.value + tag_suffix = token.suffix + tag_mark = token.start_mark + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + } else if token.typ == yaml_TAG_TOKEN { + tag_token = true + tag_handle = token.value + tag_suffix = token.suffix + start_mark = token.start_mark + tag_mark = token.start_mark + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_ANCHOR_TOKEN { + anchor = token.value + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + } + + var tag []byte + if tag_token { + if len(tag_handle) == 0 { + tag = tag_suffix + tag_suffix = nil + } else { + for i := range parser.tag_directives { + if bytes.Equal(parser.tag_directives[i].handle, tag_handle) { + tag = append([]byte(nil), parser.tag_directives[i].prefix...) + tag = append(tag, tag_suffix...) + break + } + } + if len(tag) == 0 { + yaml_parser_set_parser_error_context(parser, + "while parsing a node", start_mark, + "found undefined tag handle", tag_mark) + return false + } + } + } + + implicit := len(tag) == 0 + if indentless_sequence && token.typ == yaml_BLOCK_ENTRY_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_BLOCK_SEQUENCE_STYLE), + } + return true + } + if token.typ == yaml_SCALAR_TOKEN { + var plain_implicit, quoted_implicit bool + end_mark = token.end_mark + if (len(tag) == 0 && token.style == yaml_PLAIN_SCALAR_STYLE) || (len(tag) == 1 && tag[0] == '!') { + plain_implicit = true + } else if len(tag) == 0 { + quoted_implicit = true + } + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + value: token.value, + implicit: plain_implicit, + quoted_implicit: quoted_implicit, + style: yaml_style_t(token.style), + } + yaml_parser_set_event_comments(parser, event) + skip_token(parser) + return true + } + if token.typ == yaml_FLOW_SEQUENCE_START_TOKEN { + // [Go] Some of the events below can be merged as they differ only on style. + end_mark = token.end_mark + parser.state = yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_FLOW_SEQUENCE_STYLE), + } + yaml_parser_set_event_comments(parser, event) + return true + } + if token.typ == yaml_FLOW_MAPPING_START_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_FLOW_MAPPING_STYLE), + } + yaml_parser_set_event_comments(parser, event) + return true + } + if block && token.typ == yaml_BLOCK_SEQUENCE_START_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_BLOCK_SEQUENCE_STYLE), + } + if parser.stem_comment != nil { + event.head_comment = parser.stem_comment + parser.stem_comment = nil + } + return true + } + if block && token.typ == yaml_BLOCK_MAPPING_START_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_BLOCK_MAPPING_STYLE), + } + if parser.stem_comment != nil { + event.head_comment = parser.stem_comment + parser.stem_comment = nil + } + return true + } + if len(anchor) > 0 || len(tag) > 0 { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + quoted_implicit: false, + style: yaml_style_t(yaml_PLAIN_SCALAR_STYLE), + } + return true + } + + context := "while parsing a flow node" + if block { + context = "while parsing a block node" + } + yaml_parser_set_parser_error_context(parser, context, start_mark, + "did not find expected node content", token.start_mark) + return false +} + +// Parse the productions: +// block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END +// +// ******************** *********** * ********* +func yaml_parser_parse_block_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + if token == nil { + return false + } + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_BLOCK_ENTRY_TOKEN { + mark := token.end_mark + prior_head_len := len(parser.head_comment) + skip_token(parser) + yaml_parser_split_stem_comment(parser, prior_head_len) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_BLOCK_ENTRY_TOKEN && token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE) + return yaml_parser_parse_node(parser, event, true, false) + } else { + parser.state = yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + } + if token.typ == yaml_BLOCK_END_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + + skip_token(parser) + return true + } + + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a block collection", context_mark, + "did not find expected '-' indicator", token.start_mark) +} + +// Parse the productions: +// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ +// +// *********** * +func yaml_parser_parse_indentless_sequence_entry(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_BLOCK_ENTRY_TOKEN { + mark := token.end_mark + prior_head_len := len(parser.head_comment) + skip_token(parser) + yaml_parser_split_stem_comment(parser, prior_head_len) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_BLOCK_ENTRY_TOKEN && + token.typ != yaml_KEY_TOKEN && + token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE) + return yaml_parser_parse_node(parser, event, true, false) + } + parser.state = yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + start_mark: token.start_mark, + end_mark: token.start_mark, // [Go] Shouldn't this be token.end_mark? + } + return true +} + +// Split stem comment from head comment. +// +// When a sequence or map is found under a sequence entry, the former head comment +// is assigned to the underlying sequence or map as a whole, not the individual +// sequence or map entry as would be expected otherwise. To handle this case the +// previous head comment is moved aside as the stem comment. +func yaml_parser_split_stem_comment(parser *yaml_parser_t, stem_len int) { + if stem_len == 0 { + return + } + + token := peek_token(parser) + if token == nil || token.typ != yaml_BLOCK_SEQUENCE_START_TOKEN && token.typ != yaml_BLOCK_MAPPING_START_TOKEN { + return + } + + parser.stem_comment = parser.head_comment[:stem_len] + if len(parser.head_comment) == stem_len { + parser.head_comment = nil + } else { + // Copy suffix to prevent very strange bugs if someone ever appends + // further bytes to the prefix in the stem_comment slice above. + parser.head_comment = append([]byte(nil), parser.head_comment[stem_len+1:]...) + } +} + +// Parse the productions: +// block_mapping ::= BLOCK-MAPPING_START +// +// ******************* +// ((KEY block_node_or_indentless_sequence?)? +// *** * +// (VALUE block_node_or_indentless_sequence?)?)* +// +// BLOCK-END +// ********* +func yaml_parser_parse_block_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + if token == nil { + return false + } + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + + token := peek_token(parser) + if token == nil { + return false + } + + // [Go] A tail comment was left from the prior mapping value processed. Emit an event + // as it needs to be processed with that value and not the following key. + if len(parser.tail_comment) > 0 { + *event = yaml_event_t{ + typ: yaml_TAIL_COMMENT_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + foot_comment: parser.tail_comment, + } + parser.tail_comment = nil + return true + } + + if token.typ == yaml_KEY_TOKEN { + mark := token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_KEY_TOKEN && + token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_BLOCK_MAPPING_VALUE_STATE) + return yaml_parser_parse_node(parser, event, true, true) + } else { + parser.state = yaml_PARSE_BLOCK_MAPPING_VALUE_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + } else if token.typ == yaml_BLOCK_END_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + yaml_parser_set_event_comments(parser, event) + skip_token(parser) + return true + } + + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a block mapping", context_mark, + "did not find expected key", token.start_mark) +} + +// Parse the productions: +// block_mapping ::= BLOCK-MAPPING_START +// +// ((KEY block_node_or_indentless_sequence?)? +// +// (VALUE block_node_or_indentless_sequence?)?)* +// ***** * +// BLOCK-END +func yaml_parser_parse_block_mapping_value(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_VALUE_TOKEN { + mark := token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_KEY_TOKEN && + token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_BLOCK_MAPPING_KEY_STATE) + return yaml_parser_parse_node(parser, event, true, true) + } + parser.state = yaml_PARSE_BLOCK_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + parser.state = yaml_PARSE_BLOCK_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) +} + +// Parse the productions: +// flow_sequence ::= FLOW-SEQUENCE-START +// +// ******************* +// (flow_sequence_entry FLOW-ENTRY)* +// * ********** +// flow_sequence_entry? +// * +// FLOW-SEQUENCE-END +// ***************** +// +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// +// * +func yaml_parser_parse_flow_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + if token == nil { + return false + } + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + if !first { + if token.typ == yaml_FLOW_ENTRY_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } else { + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a flow sequence", context_mark, + "did not find expected ',' or ']'", token.start_mark) + } + } + + if token.typ == yaml_KEY_TOKEN { + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + implicit: true, + style: yaml_style_t(yaml_FLOW_MAPPING_STYLE), + } + skip_token(parser) + return true + } else if token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + yaml_parser_set_event_comments(parser, event) + + skip_token(parser) + return true +} + +// Parse the productions: +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// +// *** * +func yaml_parser_parse_flow_sequence_entry_mapping_key(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_FLOW_ENTRY_TOKEN && + token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + mark := token.end_mark + skip_token(parser) + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) +} + +// Parse the productions: +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// +// ***** * +func yaml_parser_parse_flow_sequence_entry_mapping_value(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_VALUE_TOKEN { + skip_token(parser) + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_FLOW_ENTRY_TOKEN && token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) +} + +// Parse the productions: +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// +// * +func yaml_parser_parse_flow_sequence_entry_mapping_end(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + start_mark: token.start_mark, + end_mark: token.start_mark, // [Go] Shouldn't this be end_mark? + } + return true +} + +// Parse the productions: +// flow_mapping ::= FLOW-MAPPING-START +// +// ****************** +// (flow_mapping_entry FLOW-ENTRY)* +// * ********** +// flow_mapping_entry? +// ****************** +// FLOW-MAPPING-END +// **************** +// +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// - *** * +func yaml_parser_parse_flow_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ != yaml_FLOW_MAPPING_END_TOKEN { + if !first { + if token.typ == yaml_FLOW_ENTRY_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } else { + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a flow mapping", context_mark, + "did not find expected ',' or '}'", token.start_mark) + } + } + + if token.typ == yaml_KEY_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_FLOW_ENTRY_TOKEN && + token.typ != yaml_FLOW_MAPPING_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_MAPPING_VALUE_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } else { + parser.state = yaml_PARSE_FLOW_MAPPING_VALUE_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) + } + } else if token.typ != yaml_FLOW_MAPPING_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + yaml_parser_set_event_comments(parser, event) + skip_token(parser) + return true +} + +// Parse the productions: +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// - ***** * +func yaml_parser_parse_flow_mapping_value(parser *yaml_parser_t, event *yaml_event_t, empty bool) bool { + token := peek_token(parser) + if token == nil { + return false + } + if empty { + parser.state = yaml_PARSE_FLOW_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) + } + if token.typ == yaml_VALUE_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_FLOW_ENTRY_TOKEN && token.typ != yaml_FLOW_MAPPING_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_MAPPING_KEY_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + parser.state = yaml_PARSE_FLOW_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) +} + +// Generate an empty scalar event. +func yaml_parser_process_empty_scalar(parser *yaml_parser_t, event *yaml_event_t, mark yaml_mark_t) bool { + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + start_mark: mark, + end_mark: mark, + value: nil, // Empty + implicit: true, + style: yaml_style_t(yaml_PLAIN_SCALAR_STYLE), + } + return true +} + +var default_tag_directives = []yaml_tag_directive_t{ + {[]byte("!"), []byte("!")}, + {[]byte("!!"), []byte("tag:yaml.org,2002:")}, +} + +// Parse directives. +func yaml_parser_process_directives(parser *yaml_parser_t, + version_directive_ref **yaml_version_directive_t, + tag_directives_ref *[]yaml_tag_directive_t) bool { + + var version_directive *yaml_version_directive_t + var tag_directives []yaml_tag_directive_t + + token := peek_token(parser) + if token == nil { + return false + } + + for token.typ == yaml_VERSION_DIRECTIVE_TOKEN || token.typ == yaml_TAG_DIRECTIVE_TOKEN { + if token.typ == yaml_VERSION_DIRECTIVE_TOKEN { + if version_directive != nil { + yaml_parser_set_parser_error(parser, + "found duplicate %YAML directive", token.start_mark) + return false + } + if token.major != 1 || token.minor != 1 { + yaml_parser_set_parser_error(parser, + "found incompatible YAML document", token.start_mark) + return false + } + version_directive = &yaml_version_directive_t{ + major: token.major, + minor: token.minor, + } + } else if token.typ == yaml_TAG_DIRECTIVE_TOKEN { + value := yaml_tag_directive_t{ + handle: token.value, + prefix: token.prefix, + } + if !yaml_parser_append_tag_directive(parser, value, false, token.start_mark) { + return false + } + tag_directives = append(tag_directives, value) + } + + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + + for i := range default_tag_directives { + if !yaml_parser_append_tag_directive(parser, default_tag_directives[i], true, token.start_mark) { + return false + } + } + + if version_directive_ref != nil { + *version_directive_ref = version_directive + } + if tag_directives_ref != nil { + *tag_directives_ref = tag_directives + } + return true +} + +// Append a tag directive to the directives stack. +func yaml_parser_append_tag_directive(parser *yaml_parser_t, value yaml_tag_directive_t, allow_duplicates bool, mark yaml_mark_t) bool { + for i := range parser.tag_directives { + if bytes.Equal(value.handle, parser.tag_directives[i].handle) { + if allow_duplicates { + return true + } + return yaml_parser_set_parser_error(parser, "found duplicate %TAG directive", mark) + } + } + + // [Go] I suspect the copy is unnecessary. This was likely done + // because there was no way to track ownership of the data. + value_copy := yaml_tag_directive_t{ + handle: make([]byte, len(value.handle)), + prefix: make([]byte, len(value.prefix)), + } + copy(value_copy.handle, value.handle) + copy(value_copy.prefix, value.prefix) + parser.tag_directives = append(parser.tag_directives, value_copy) + return true +} diff --git a/vendor/go.yaml.in/yaml/v3/readerc.go b/vendor/go.yaml.in/yaml/v3/readerc.go new file mode 100644 index 0000000000000..56af245366f28 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v3/readerc.go @@ -0,0 +1,434 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// Copyright (c) 2006-2010 Kirill Simonov +// +// 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. + +package yaml + +import ( + "io" +) + +// Set the reader error and return 0. +func yaml_parser_set_reader_error(parser *yaml_parser_t, problem string, offset int, value int) bool { + parser.error = yaml_READER_ERROR + parser.problem = problem + parser.problem_offset = offset + parser.problem_value = value + return false +} + +// Byte order marks. +const ( + bom_UTF8 = "\xef\xbb\xbf" + bom_UTF16LE = "\xff\xfe" + bom_UTF16BE = "\xfe\xff" +) + +// Determine the input stream encoding by checking the BOM symbol. If no BOM is +// found, the UTF-8 encoding is assumed. Return 1 on success, 0 on failure. +func yaml_parser_determine_encoding(parser *yaml_parser_t) bool { + // Ensure that we had enough bytes in the raw buffer. + for !parser.eof && len(parser.raw_buffer)-parser.raw_buffer_pos < 3 { + if !yaml_parser_update_raw_buffer(parser) { + return false + } + } + + // Determine the encoding. + buf := parser.raw_buffer + pos := parser.raw_buffer_pos + avail := len(buf) - pos + if avail >= 2 && buf[pos] == bom_UTF16LE[0] && buf[pos+1] == bom_UTF16LE[1] { + parser.encoding = yaml_UTF16LE_ENCODING + parser.raw_buffer_pos += 2 + parser.offset += 2 + } else if avail >= 2 && buf[pos] == bom_UTF16BE[0] && buf[pos+1] == bom_UTF16BE[1] { + parser.encoding = yaml_UTF16BE_ENCODING + parser.raw_buffer_pos += 2 + parser.offset += 2 + } else if avail >= 3 && buf[pos] == bom_UTF8[0] && buf[pos+1] == bom_UTF8[1] && buf[pos+2] == bom_UTF8[2] { + parser.encoding = yaml_UTF8_ENCODING + parser.raw_buffer_pos += 3 + parser.offset += 3 + } else { + parser.encoding = yaml_UTF8_ENCODING + } + return true +} + +// Update the raw buffer. +func yaml_parser_update_raw_buffer(parser *yaml_parser_t) bool { + size_read := 0 + + // Return if the raw buffer is full. + if parser.raw_buffer_pos == 0 && len(parser.raw_buffer) == cap(parser.raw_buffer) { + return true + } + + // Return on EOF. + if parser.eof { + return true + } + + // Move the remaining bytes in the raw buffer to the beginning. + if parser.raw_buffer_pos > 0 && parser.raw_buffer_pos < len(parser.raw_buffer) { + copy(parser.raw_buffer, parser.raw_buffer[parser.raw_buffer_pos:]) + } + parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)-parser.raw_buffer_pos] + parser.raw_buffer_pos = 0 + + // Call the read handler to fill the buffer. + size_read, err := parser.read_handler(parser, parser.raw_buffer[len(parser.raw_buffer):cap(parser.raw_buffer)]) + parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)+size_read] + if err == io.EOF { + parser.eof = true + } else if err != nil { + return yaml_parser_set_reader_error(parser, "input error: "+err.Error(), parser.offset, -1) + } + return true +} + +// Ensure that the buffer contains at least `length` characters. +// Return true on success, false on failure. +// +// The length is supposed to be significantly less that the buffer size. +func yaml_parser_update_buffer(parser *yaml_parser_t, length int) bool { + if parser.read_handler == nil { + panic("read handler must be set") + } + + // [Go] This function was changed to guarantee the requested length size at EOF. + // The fact we need to do this is pretty awful, but the description above implies + // for that to be the case, and there are tests + + // If the EOF flag is set and the raw buffer is empty, do nothing. + if parser.eof && parser.raw_buffer_pos == len(parser.raw_buffer) { + // [Go] ACTUALLY! Read the documentation of this function above. + // This is just broken. To return true, we need to have the + // given length in the buffer. Not doing that means every single + // check that calls this function to make sure the buffer has a + // given length is Go) panicking; or C) accessing invalid memory. + //return true + } + + // Return if the buffer contains enough characters. + if parser.unread >= length { + return true + } + + // Determine the input encoding if it is not known yet. + if parser.encoding == yaml_ANY_ENCODING { + if !yaml_parser_determine_encoding(parser) { + return false + } + } + + // Move the unread characters to the beginning of the buffer. + buffer_len := len(parser.buffer) + if parser.buffer_pos > 0 && parser.buffer_pos < buffer_len { + copy(parser.buffer, parser.buffer[parser.buffer_pos:]) + buffer_len -= parser.buffer_pos + parser.buffer_pos = 0 + } else if parser.buffer_pos == buffer_len { + buffer_len = 0 + parser.buffer_pos = 0 + } + + // Open the whole buffer for writing, and cut it before returning. + parser.buffer = parser.buffer[:cap(parser.buffer)] + + // Fill the buffer until it has enough characters. + first := true + for parser.unread < length { + + // Fill the raw buffer if necessary. + if !first || parser.raw_buffer_pos == len(parser.raw_buffer) { + if !yaml_parser_update_raw_buffer(parser) { + parser.buffer = parser.buffer[:buffer_len] + return false + } + } + first = false + + // Decode the raw buffer. + inner: + for parser.raw_buffer_pos != len(parser.raw_buffer) { + var value rune + var width int + + raw_unread := len(parser.raw_buffer) - parser.raw_buffer_pos + + // Decode the next character. + switch parser.encoding { + case yaml_UTF8_ENCODING: + // Decode a UTF-8 character. Check RFC 3629 + // (http://www.ietf.org/rfc/rfc3629.txt) for more details. + // + // The following table (taken from the RFC) is used for + // decoding. + // + // Char. number range | UTF-8 octet sequence + // (hexadecimal) | (binary) + // --------------------+------------------------------------ + // 0000 0000-0000 007F | 0xxxxxxx + // 0000 0080-0000 07FF | 110xxxxx 10xxxxxx + // 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx + // 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + // + // Additionally, the characters in the range 0xD800-0xDFFF + // are prohibited as they are reserved for use with UTF-16 + // surrogate pairs. + + // Determine the length of the UTF-8 sequence. + octet := parser.raw_buffer[parser.raw_buffer_pos] + switch { + case octet&0x80 == 0x00: + width = 1 + case octet&0xE0 == 0xC0: + width = 2 + case octet&0xF0 == 0xE0: + width = 3 + case octet&0xF8 == 0xF0: + width = 4 + default: + // The leading octet is invalid. + return yaml_parser_set_reader_error(parser, + "invalid leading UTF-8 octet", + parser.offset, int(octet)) + } + + // Check if the raw buffer contains an incomplete character. + if width > raw_unread { + if parser.eof { + return yaml_parser_set_reader_error(parser, + "incomplete UTF-8 octet sequence", + parser.offset, -1) + } + break inner + } + + // Decode the leading octet. + switch { + case octet&0x80 == 0x00: + value = rune(octet & 0x7F) + case octet&0xE0 == 0xC0: + value = rune(octet & 0x1F) + case octet&0xF0 == 0xE0: + value = rune(octet & 0x0F) + case octet&0xF8 == 0xF0: + value = rune(octet & 0x07) + default: + value = 0 + } + + // Check and decode the trailing octets. + for k := 1; k < width; k++ { + octet = parser.raw_buffer[parser.raw_buffer_pos+k] + + // Check if the octet is valid. + if (octet & 0xC0) != 0x80 { + return yaml_parser_set_reader_error(parser, + "invalid trailing UTF-8 octet", + parser.offset+k, int(octet)) + } + + // Decode the octet. + value = (value << 6) + rune(octet&0x3F) + } + + // Check the length of the sequence against the value. + switch { + case width == 1: + case width == 2 && value >= 0x80: + case width == 3 && value >= 0x800: + case width == 4 && value >= 0x10000: + default: + return yaml_parser_set_reader_error(parser, + "invalid length of a UTF-8 sequence", + parser.offset, -1) + } + + // Check the range of the value. + if value >= 0xD800 && value <= 0xDFFF || value > 0x10FFFF { + return yaml_parser_set_reader_error(parser, + "invalid Unicode character", + parser.offset, int(value)) + } + + case yaml_UTF16LE_ENCODING, yaml_UTF16BE_ENCODING: + var low, high int + if parser.encoding == yaml_UTF16LE_ENCODING { + low, high = 0, 1 + } else { + low, high = 1, 0 + } + + // The UTF-16 encoding is not as simple as one might + // naively think. Check RFC 2781 + // (http://www.ietf.org/rfc/rfc2781.txt). + // + // Normally, two subsequent bytes describe a Unicode + // character. However a special technique (called a + // surrogate pair) is used for specifying character + // values larger than 0xFFFF. + // + // A surrogate pair consists of two pseudo-characters: + // high surrogate area (0xD800-0xDBFF) + // low surrogate area (0xDC00-0xDFFF) + // + // The following formulas are used for decoding + // and encoding characters using surrogate pairs: + // + // U = U' + 0x10000 (0x01 00 00 <= U <= 0x10 FF FF) + // U' = yyyyyyyyyyxxxxxxxxxx (0 <= U' <= 0x0F FF FF) + // W1 = 110110yyyyyyyyyy + // W2 = 110111xxxxxxxxxx + // + // where U is the character value, W1 is the high surrogate + // area, W2 is the low surrogate area. + + // Check for incomplete UTF-16 character. + if raw_unread < 2 { + if parser.eof { + return yaml_parser_set_reader_error(parser, + "incomplete UTF-16 character", + parser.offset, -1) + } + break inner + } + + // Get the character. + value = rune(parser.raw_buffer[parser.raw_buffer_pos+low]) + + (rune(parser.raw_buffer[parser.raw_buffer_pos+high]) << 8) + + // Check for unexpected low surrogate area. + if value&0xFC00 == 0xDC00 { + return yaml_parser_set_reader_error(parser, + "unexpected low surrogate area", + parser.offset, int(value)) + } + + // Check for a high surrogate area. + if value&0xFC00 == 0xD800 { + width = 4 + + // Check for incomplete surrogate pair. + if raw_unread < 4 { + if parser.eof { + return yaml_parser_set_reader_error(parser, + "incomplete UTF-16 surrogate pair", + parser.offset, -1) + } + break inner + } + + // Get the next character. + value2 := rune(parser.raw_buffer[parser.raw_buffer_pos+low+2]) + + (rune(parser.raw_buffer[parser.raw_buffer_pos+high+2]) << 8) + + // Check for a low surrogate area. + if value2&0xFC00 != 0xDC00 { + return yaml_parser_set_reader_error(parser, + "expected low surrogate area", + parser.offset+2, int(value2)) + } + + // Generate the value of the surrogate pair. + value = 0x10000 + ((value & 0x3FF) << 10) + (value2 & 0x3FF) + } else { + width = 2 + } + + default: + panic("impossible") + } + + // Check if the character is in the allowed range: + // #x9 | #xA | #xD | [#x20-#x7E] (8 bit) + // | #x85 | [#xA0-#xD7FF] | [#xE000-#xFFFD] (16 bit) + // | [#x10000-#x10FFFF] (32 bit) + switch { + case value == 0x09: + case value == 0x0A: + case value == 0x0D: + case value >= 0x20 && value <= 0x7E: + case value == 0x85: + case value >= 0xA0 && value <= 0xD7FF: + case value >= 0xE000 && value <= 0xFFFD: + case value >= 0x10000 && value <= 0x10FFFF: + default: + return yaml_parser_set_reader_error(parser, + "control characters are not allowed", + parser.offset, int(value)) + } + + // Move the raw pointers. + parser.raw_buffer_pos += width + parser.offset += width + + // Finally put the character into the buffer. + if value <= 0x7F { + // 0000 0000-0000 007F . 0xxxxxxx + parser.buffer[buffer_len+0] = byte(value) + buffer_len += 1 + } else if value <= 0x7FF { + // 0000 0080-0000 07FF . 110xxxxx 10xxxxxx + parser.buffer[buffer_len+0] = byte(0xC0 + (value >> 6)) + parser.buffer[buffer_len+1] = byte(0x80 + (value & 0x3F)) + buffer_len += 2 + } else if value <= 0xFFFF { + // 0000 0800-0000 FFFF . 1110xxxx 10xxxxxx 10xxxxxx + parser.buffer[buffer_len+0] = byte(0xE0 + (value >> 12)) + parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 6) & 0x3F)) + parser.buffer[buffer_len+2] = byte(0x80 + (value & 0x3F)) + buffer_len += 3 + } else { + // 0001 0000-0010 FFFF . 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + parser.buffer[buffer_len+0] = byte(0xF0 + (value >> 18)) + parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 12) & 0x3F)) + parser.buffer[buffer_len+2] = byte(0x80 + ((value >> 6) & 0x3F)) + parser.buffer[buffer_len+3] = byte(0x80 + (value & 0x3F)) + buffer_len += 4 + } + + parser.unread++ + } + + // On EOF, put NUL into the buffer and return. + if parser.eof { + parser.buffer[buffer_len] = 0 + buffer_len++ + parser.unread++ + break + } + } + // [Go] Read the documentation of this function above. To return true, + // we need to have the given length in the buffer. Not doing that means + // every single check that calls this function to make sure the buffer + // has a given length is Go) panicking; or C) accessing invalid memory. + // This happens here due to the EOF above breaking early. + for buffer_len < length { + parser.buffer[buffer_len] = 0 + buffer_len++ + } + parser.buffer = parser.buffer[:buffer_len] + return true +} diff --git a/vendor/go.yaml.in/yaml/v3/resolve.go b/vendor/go.yaml.in/yaml/v3/resolve.go new file mode 100644 index 0000000000000..64ae888057a5a --- /dev/null +++ b/vendor/go.yaml.in/yaml/v3/resolve.go @@ -0,0 +1,326 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// +// 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 yaml + +import ( + "encoding/base64" + "math" + "regexp" + "strconv" + "strings" + "time" +) + +type resolveMapItem struct { + value interface{} + tag string +} + +var resolveTable = make([]byte, 256) +var resolveMap = make(map[string]resolveMapItem) + +func init() { + t := resolveTable + t[int('+')] = 'S' // Sign + t[int('-')] = 'S' + for _, c := range "0123456789" { + t[int(c)] = 'D' // Digit + } + for _, c := range "yYnNtTfFoO~" { + t[int(c)] = 'M' // In map + } + t[int('.')] = '.' // Float (potentially in map) + + var resolveMapList = []struct { + v interface{} + tag string + l []string + }{ + {true, boolTag, []string{"true", "True", "TRUE"}}, + {false, boolTag, []string{"false", "False", "FALSE"}}, + {nil, nullTag, []string{"", "~", "null", "Null", "NULL"}}, + {math.NaN(), floatTag, []string{".nan", ".NaN", ".NAN"}}, + {math.Inf(+1), floatTag, []string{".inf", ".Inf", ".INF"}}, + {math.Inf(+1), floatTag, []string{"+.inf", "+.Inf", "+.INF"}}, + {math.Inf(-1), floatTag, []string{"-.inf", "-.Inf", "-.INF"}}, + {"<<", mergeTag, []string{"<<"}}, + } + + m := resolveMap + for _, item := range resolveMapList { + for _, s := range item.l { + m[s] = resolveMapItem{item.v, item.tag} + } + } +} + +const ( + nullTag = "!!null" + boolTag = "!!bool" + strTag = "!!str" + intTag = "!!int" + floatTag = "!!float" + timestampTag = "!!timestamp" + seqTag = "!!seq" + mapTag = "!!map" + binaryTag = "!!binary" + mergeTag = "!!merge" +) + +var longTags = make(map[string]string) +var shortTags = make(map[string]string) + +func init() { + for _, stag := range []string{nullTag, boolTag, strTag, intTag, floatTag, timestampTag, seqTag, mapTag, binaryTag, mergeTag} { + ltag := longTag(stag) + longTags[stag] = ltag + shortTags[ltag] = stag + } +} + +const longTagPrefix = "tag:yaml.org,2002:" + +func shortTag(tag string) string { + if strings.HasPrefix(tag, longTagPrefix) { + if stag, ok := shortTags[tag]; ok { + return stag + } + return "!!" + tag[len(longTagPrefix):] + } + return tag +} + +func longTag(tag string) string { + if strings.HasPrefix(tag, "!!") { + if ltag, ok := longTags[tag]; ok { + return ltag + } + return longTagPrefix + tag[2:] + } + return tag +} + +func resolvableTag(tag string) bool { + switch tag { + case "", strTag, boolTag, intTag, floatTag, nullTag, timestampTag: + return true + } + return false +} + +var yamlStyleFloat = regexp.MustCompile(`^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$`) + +func resolve(tag string, in string) (rtag string, out interface{}) { + tag = shortTag(tag) + if !resolvableTag(tag) { + return tag, in + } + + defer func() { + switch tag { + case "", rtag, strTag, binaryTag: + return + case floatTag: + if rtag == intTag { + switch v := out.(type) { + case int64: + rtag = floatTag + out = float64(v) + return + case int: + rtag = floatTag + out = float64(v) + return + } + } + } + failf("cannot decode %s `%s` as a %s", shortTag(rtag), in, shortTag(tag)) + }() + + // Any data is accepted as a !!str or !!binary. + // Otherwise, the prefix is enough of a hint about what it might be. + hint := byte('N') + if in != "" { + hint = resolveTable[in[0]] + } + if hint != 0 && tag != strTag && tag != binaryTag { + // Handle things we can lookup in a map. + if item, ok := resolveMap[in]; ok { + return item.tag, item.value + } + + // Base 60 floats are a bad idea, were dropped in YAML 1.2, and + // are purposefully unsupported here. They're still quoted on + // the way out for compatibility with other parser, though. + + switch hint { + case 'M': + // We've already checked the map above. + + case '.': + // Not in the map, so maybe a normal float. + floatv, err := strconv.ParseFloat(in, 64) + if err == nil { + return floatTag, floatv + } + + case 'D', 'S': + // Int, float, or timestamp. + // Only try values as a timestamp if the value is unquoted or there's an explicit + // !!timestamp tag. + if tag == "" || tag == timestampTag { + t, ok := parseTimestamp(in) + if ok { + return timestampTag, t + } + } + + plain := strings.Replace(in, "_", "", -1) + intv, err := strconv.ParseInt(plain, 0, 64) + if err == nil { + if intv == int64(int(intv)) { + return intTag, int(intv) + } else { + return intTag, intv + } + } + uintv, err := strconv.ParseUint(plain, 0, 64) + if err == nil { + return intTag, uintv + } + if yamlStyleFloat.MatchString(plain) { + floatv, err := strconv.ParseFloat(plain, 64) + if err == nil { + return floatTag, floatv + } + } + if strings.HasPrefix(plain, "0b") { + intv, err := strconv.ParseInt(plain[2:], 2, 64) + if err == nil { + if intv == int64(int(intv)) { + return intTag, int(intv) + } else { + return intTag, intv + } + } + uintv, err := strconv.ParseUint(plain[2:], 2, 64) + if err == nil { + return intTag, uintv + } + } else if strings.HasPrefix(plain, "-0b") { + intv, err := strconv.ParseInt("-"+plain[3:], 2, 64) + if err == nil { + if true || intv == int64(int(intv)) { + return intTag, int(intv) + } else { + return intTag, intv + } + } + } + // Octals as introduced in version 1.2 of the spec. + // Octals from the 1.1 spec, spelled as 0777, are still + // decoded by default in v3 as well for compatibility. + // May be dropped in v4 depending on how usage evolves. + if strings.HasPrefix(plain, "0o") { + intv, err := strconv.ParseInt(plain[2:], 8, 64) + if err == nil { + if intv == int64(int(intv)) { + return intTag, int(intv) + } else { + return intTag, intv + } + } + uintv, err := strconv.ParseUint(plain[2:], 8, 64) + if err == nil { + return intTag, uintv + } + } else if strings.HasPrefix(plain, "-0o") { + intv, err := strconv.ParseInt("-"+plain[3:], 8, 64) + if err == nil { + if true || intv == int64(int(intv)) { + return intTag, int(intv) + } else { + return intTag, intv + } + } + } + default: + panic("internal error: missing handler for resolver table: " + string(rune(hint)) + " (with " + in + ")") + } + } + return strTag, in +} + +// encodeBase64 encodes s as base64 that is broken up into multiple lines +// as appropriate for the resulting length. +func encodeBase64(s string) string { + const lineLen = 70 + encLen := base64.StdEncoding.EncodedLen(len(s)) + lines := encLen/lineLen + 1 + buf := make([]byte, encLen*2+lines) + in := buf[0:encLen] + out := buf[encLen:] + base64.StdEncoding.Encode(in, []byte(s)) + k := 0 + for i := 0; i < len(in); i += lineLen { + j := i + lineLen + if j > len(in) { + j = len(in) + } + k += copy(out[k:], in[i:j]) + if lines > 1 { + out[k] = '\n' + k++ + } + } + return string(out[:k]) +} + +// This is a subset of the formats allowed by the regular expression +// defined at http://yaml.org/type/timestamp.html. +var allowedTimestampFormats = []string{ + "2006-1-2T15:4:5.999999999Z07:00", // RCF3339Nano with short date fields. + "2006-1-2t15:4:5.999999999Z07:00", // RFC3339Nano with short date fields and lower-case "t". + "2006-1-2 15:4:5.999999999", // space separated with no time zone + "2006-1-2", // date only + // Notable exception: time.Parse cannot handle: "2001-12-14 21:59:43.10 -5" + // from the set of examples. +} + +// parseTimestamp parses s as a timestamp string and +// returns the timestamp and reports whether it succeeded. +// Timestamp formats are defined at http://yaml.org/type/timestamp.html +func parseTimestamp(s string) (time.Time, bool) { + // TODO write code to check all the formats supported by + // http://yaml.org/type/timestamp.html instead of using time.Parse. + + // Quick check: all date formats start with YYYY-. + i := 0 + for ; i < len(s); i++ { + if c := s[i]; c < '0' || c > '9' { + break + } + } + if i != 4 || i == len(s) || s[i] != '-' { + return time.Time{}, false + } + for _, format := range allowedTimestampFormats { + if t, err := time.Parse(format, s); err == nil { + return t, true + } + } + return time.Time{}, false +} diff --git a/vendor/go.yaml.in/yaml/v3/scannerc.go b/vendor/go.yaml.in/yaml/v3/scannerc.go new file mode 100644 index 0000000000000..30b1f08920a0d --- /dev/null +++ b/vendor/go.yaml.in/yaml/v3/scannerc.go @@ -0,0 +1,3040 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// Copyright (c) 2006-2010 Kirill Simonov +// +// 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. + +package yaml + +import ( + "bytes" + "fmt" +) + +// Introduction +// ************ +// +// The following notes assume that you are familiar with the YAML specification +// (http://yaml.org/spec/1.2/spec.html). We mostly follow it, although in +// some cases we are less restrictive that it requires. +// +// The process of transforming a YAML stream into a sequence of events is +// divided on two steps: Scanning and Parsing. +// +// The Scanner transforms the input stream into a sequence of tokens, while the +// parser transform the sequence of tokens produced by the Scanner into a +// sequence of parsing events. +// +// The Scanner is rather clever and complicated. The Parser, on the contrary, +// is a straightforward implementation of a recursive-descendant parser (or, +// LL(1) parser, as it is usually called). +// +// Actually there are two issues of Scanning that might be called "clever", the +// rest is quite straightforward. The issues are "block collection start" and +// "simple keys". Both issues are explained below in details. +// +// Here the Scanning step is explained and implemented. We start with the list +// of all the tokens produced by the Scanner together with short descriptions. +// +// Now, tokens: +// +// STREAM-START(encoding) # The stream start. +// STREAM-END # The stream end. +// VERSION-DIRECTIVE(major,minor) # The '%YAML' directive. +// TAG-DIRECTIVE(handle,prefix) # The '%TAG' directive. +// DOCUMENT-START # '---' +// DOCUMENT-END # '...' +// BLOCK-SEQUENCE-START # Indentation increase denoting a block +// BLOCK-MAPPING-START # sequence or a block mapping. +// BLOCK-END # Indentation decrease. +// FLOW-SEQUENCE-START # '[' +// FLOW-SEQUENCE-END # ']' +// BLOCK-SEQUENCE-START # '{' +// BLOCK-SEQUENCE-END # '}' +// BLOCK-ENTRY # '-' +// FLOW-ENTRY # ',' +// KEY # '?' or nothing (simple keys). +// VALUE # ':' +// ALIAS(anchor) # '*anchor' +// ANCHOR(anchor) # '&anchor' +// TAG(handle,suffix) # '!handle!suffix' +// SCALAR(value,style) # A scalar. +// +// The following two tokens are "virtual" tokens denoting the beginning and the +// end of the stream: +// +// STREAM-START(encoding) +// STREAM-END +// +// We pass the information about the input stream encoding with the +// STREAM-START token. +// +// The next two tokens are responsible for tags: +// +// VERSION-DIRECTIVE(major,minor) +// TAG-DIRECTIVE(handle,prefix) +// +// Example: +// +// %YAML 1.1 +// %TAG ! !foo +// %TAG !yaml! tag:yaml.org,2002: +// --- +// +// The correspoding sequence of tokens: +// +// STREAM-START(utf-8) +// VERSION-DIRECTIVE(1,1) +// TAG-DIRECTIVE("!","!foo") +// TAG-DIRECTIVE("!yaml","tag:yaml.org,2002:") +// DOCUMENT-START +// STREAM-END +// +// Note that the VERSION-DIRECTIVE and TAG-DIRECTIVE tokens occupy a whole +// line. +// +// The document start and end indicators are represented by: +// +// DOCUMENT-START +// DOCUMENT-END +// +// Note that if a YAML stream contains an implicit document (without '---' +// and '...' indicators), no DOCUMENT-START and DOCUMENT-END tokens will be +// produced. +// +// In the following examples, we present whole documents together with the +// produced tokens. +// +// 1. An implicit document: +// +// 'a scalar' +// +// Tokens: +// +// STREAM-START(utf-8) +// SCALAR("a scalar",single-quoted) +// STREAM-END +// +// 2. An explicit document: +// +// --- +// 'a scalar' +// ... +// +// Tokens: +// +// STREAM-START(utf-8) +// DOCUMENT-START +// SCALAR("a scalar",single-quoted) +// DOCUMENT-END +// STREAM-END +// +// 3. Several documents in a stream: +// +// 'a scalar' +// --- +// 'another scalar' +// --- +// 'yet another scalar' +// +// Tokens: +// +// STREAM-START(utf-8) +// SCALAR("a scalar",single-quoted) +// DOCUMENT-START +// SCALAR("another scalar",single-quoted) +// DOCUMENT-START +// SCALAR("yet another scalar",single-quoted) +// STREAM-END +// +// We have already introduced the SCALAR token above. The following tokens are +// used to describe aliases, anchors, tag, and scalars: +// +// ALIAS(anchor) +// ANCHOR(anchor) +// TAG(handle,suffix) +// SCALAR(value,style) +// +// The following series of examples illustrate the usage of these tokens: +// +// 1. A recursive sequence: +// +// &A [ *A ] +// +// Tokens: +// +// STREAM-START(utf-8) +// ANCHOR("A") +// FLOW-SEQUENCE-START +// ALIAS("A") +// FLOW-SEQUENCE-END +// STREAM-END +// +// 2. A tagged scalar: +// +// !!float "3.14" # A good approximation. +// +// Tokens: +// +// STREAM-START(utf-8) +// TAG("!!","float") +// SCALAR("3.14",double-quoted) +// STREAM-END +// +// 3. Various scalar styles: +// +// --- # Implicit empty plain scalars do not produce tokens. +// --- a plain scalar +// --- 'a single-quoted scalar' +// --- "a double-quoted scalar" +// --- |- +// a literal scalar +// --- >- +// a folded +// scalar +// +// Tokens: +// +// STREAM-START(utf-8) +// DOCUMENT-START +// DOCUMENT-START +// SCALAR("a plain scalar",plain) +// DOCUMENT-START +// SCALAR("a single-quoted scalar",single-quoted) +// DOCUMENT-START +// SCALAR("a double-quoted scalar",double-quoted) +// DOCUMENT-START +// SCALAR("a literal scalar",literal) +// DOCUMENT-START +// SCALAR("a folded scalar",folded) +// STREAM-END +// +// Now it's time to review collection-related tokens. We will start with +// flow collections: +// +// FLOW-SEQUENCE-START +// FLOW-SEQUENCE-END +// FLOW-MAPPING-START +// FLOW-MAPPING-END +// FLOW-ENTRY +// KEY +// VALUE +// +// The tokens FLOW-SEQUENCE-START, FLOW-SEQUENCE-END, FLOW-MAPPING-START, and +// FLOW-MAPPING-END represent the indicators '[', ']', '{', and '}' +// correspondingly. FLOW-ENTRY represent the ',' indicator. Finally the +// indicators '?' and ':', which are used for denoting mapping keys and values, +// are represented by the KEY and VALUE tokens. +// +// The following examples show flow collections: +// +// 1. A flow sequence: +// +// [item 1, item 2, item 3] +// +// Tokens: +// +// STREAM-START(utf-8) +// FLOW-SEQUENCE-START +// SCALAR("item 1",plain) +// FLOW-ENTRY +// SCALAR("item 2",plain) +// FLOW-ENTRY +// SCALAR("item 3",plain) +// FLOW-SEQUENCE-END +// STREAM-END +// +// 2. A flow mapping: +// +// { +// a simple key: a value, # Note that the KEY token is produced. +// ? a complex key: another value, +// } +// +// Tokens: +// +// STREAM-START(utf-8) +// FLOW-MAPPING-START +// KEY +// SCALAR("a simple key",plain) +// VALUE +// SCALAR("a value",plain) +// FLOW-ENTRY +// KEY +// SCALAR("a complex key",plain) +// VALUE +// SCALAR("another value",plain) +// FLOW-ENTRY +// FLOW-MAPPING-END +// STREAM-END +// +// A simple key is a key which is not denoted by the '?' indicator. Note that +// the Scanner still produce the KEY token whenever it encounters a simple key. +// +// For scanning block collections, the following tokens are used (note that we +// repeat KEY and VALUE here): +// +// BLOCK-SEQUENCE-START +// BLOCK-MAPPING-START +// BLOCK-END +// BLOCK-ENTRY +// KEY +// VALUE +// +// The tokens BLOCK-SEQUENCE-START and BLOCK-MAPPING-START denote indentation +// increase that precedes a block collection (cf. the INDENT token in Python). +// The token BLOCK-END denote indentation decrease that ends a block collection +// (cf. the DEDENT token in Python). However YAML has some syntax pecularities +// that makes detections of these tokens more complex. +// +// The tokens BLOCK-ENTRY, KEY, and VALUE are used to represent the indicators +// '-', '?', and ':' correspondingly. +// +// The following examples show how the tokens BLOCK-SEQUENCE-START, +// BLOCK-MAPPING-START, and BLOCK-END are emitted by the Scanner: +// +// 1. Block sequences: +// +// - item 1 +// - item 2 +// - +// - item 3.1 +// - item 3.2 +// - +// key 1: value 1 +// key 2: value 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-ENTRY +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 3.1",plain) +// BLOCK-ENTRY +// SCALAR("item 3.2",plain) +// BLOCK-END +// BLOCK-ENTRY +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// 2. Block mappings: +// +// a simple key: a value # The KEY token is produced here. +// ? a complex key +// : another value +// a mapping: +// key 1: value 1 +// key 2: value 2 +// a sequence: +// - item 1 +// - item 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-MAPPING-START +// KEY +// SCALAR("a simple key",plain) +// VALUE +// SCALAR("a value",plain) +// KEY +// SCALAR("a complex key",plain) +// VALUE +// SCALAR("another value",plain) +// KEY +// SCALAR("a mapping",plain) +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// KEY +// SCALAR("a sequence",plain) +// VALUE +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// YAML does not always require to start a new block collection from a new +// line. If the current line contains only '-', '?', and ':' indicators, a new +// block collection may start at the current line. The following examples +// illustrate this case: +// +// 1. Collections in a sequence: +// +// - - item 1 +// - item 2 +// - key 1: value 1 +// key 2: value 2 +// - ? complex key +// : complex value +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// BLOCK-ENTRY +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// BLOCK-ENTRY +// BLOCK-MAPPING-START +// KEY +// SCALAR("complex key") +// VALUE +// SCALAR("complex value") +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// 2. Collections in a mapping: +// +// ? a sequence +// : - item 1 +// - item 2 +// ? a mapping +// : key 1: value 1 +// key 2: value 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-MAPPING-START +// KEY +// SCALAR("a sequence",plain) +// VALUE +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// KEY +// SCALAR("a mapping",plain) +// VALUE +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// YAML also permits non-indented sequences if they are included into a block +// mapping. In this case, the token BLOCK-SEQUENCE-START is not produced: +// +// key: +// - item 1 # BLOCK-SEQUENCE-START is NOT produced here. +// - item 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-MAPPING-START +// KEY +// SCALAR("key",plain) +// VALUE +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// + +// Ensure that the buffer contains the required number of characters. +// Return true on success, false on failure (reader error or memory error). +func cache(parser *yaml_parser_t, length int) bool { + // [Go] This was inlined: !cache(A, B) -> unread < B && !update(A, B) + return parser.unread >= length || yaml_parser_update_buffer(parser, length) +} + +// Advance the buffer pointer. +func skip(parser *yaml_parser_t) { + if !is_blank(parser.buffer, parser.buffer_pos) { + parser.newlines = 0 + } + parser.mark.index++ + parser.mark.column++ + parser.unread-- + parser.buffer_pos += width(parser.buffer[parser.buffer_pos]) +} + +func skip_line(parser *yaml_parser_t) { + if is_crlf(parser.buffer, parser.buffer_pos) { + parser.mark.index += 2 + parser.mark.column = 0 + parser.mark.line++ + parser.unread -= 2 + parser.buffer_pos += 2 + parser.newlines++ + } else if is_break(parser.buffer, parser.buffer_pos) { + parser.mark.index++ + parser.mark.column = 0 + parser.mark.line++ + parser.unread-- + parser.buffer_pos += width(parser.buffer[parser.buffer_pos]) + parser.newlines++ + } +} + +// Copy a character to a string buffer and advance pointers. +func read(parser *yaml_parser_t, s []byte) []byte { + if !is_blank(parser.buffer, parser.buffer_pos) { + parser.newlines = 0 + } + w := width(parser.buffer[parser.buffer_pos]) + if w == 0 { + panic("invalid character sequence") + } + if len(s) == 0 { + s = make([]byte, 0, 32) + } + if w == 1 && len(s)+w <= cap(s) { + s = s[:len(s)+1] + s[len(s)-1] = parser.buffer[parser.buffer_pos] + parser.buffer_pos++ + } else { + s = append(s, parser.buffer[parser.buffer_pos:parser.buffer_pos+w]...) + parser.buffer_pos += w + } + parser.mark.index++ + parser.mark.column++ + parser.unread-- + return s +} + +// Copy a line break character to a string buffer and advance pointers. +func read_line(parser *yaml_parser_t, s []byte) []byte { + buf := parser.buffer + pos := parser.buffer_pos + switch { + case buf[pos] == '\r' && buf[pos+1] == '\n': + // CR LF . LF + s = append(s, '\n') + parser.buffer_pos += 2 + parser.mark.index++ + parser.unread-- + case buf[pos] == '\r' || buf[pos] == '\n': + // CR|LF . LF + s = append(s, '\n') + parser.buffer_pos += 1 + case buf[pos] == '\xC2' && buf[pos+1] == '\x85': + // NEL . LF + s = append(s, '\n') + parser.buffer_pos += 2 + case buf[pos] == '\xE2' && buf[pos+1] == '\x80' && (buf[pos+2] == '\xA8' || buf[pos+2] == '\xA9'): + // LS|PS . LS|PS + s = append(s, buf[parser.buffer_pos:pos+3]...) + parser.buffer_pos += 3 + default: + return s + } + parser.mark.index++ + parser.mark.column = 0 + parser.mark.line++ + parser.unread-- + parser.newlines++ + return s +} + +// Get the next token. +func yaml_parser_scan(parser *yaml_parser_t, token *yaml_token_t) bool { + // Erase the token object. + *token = yaml_token_t{} // [Go] Is this necessary? + + // No tokens after STREAM-END or error. + if parser.stream_end_produced || parser.error != yaml_NO_ERROR { + return true + } + + // Ensure that the tokens queue contains enough tokens. + if !parser.token_available { + if !yaml_parser_fetch_more_tokens(parser) { + return false + } + } + + // Fetch the next token from the queue. + *token = parser.tokens[parser.tokens_head] + parser.tokens_head++ + parser.tokens_parsed++ + parser.token_available = false + + if token.typ == yaml_STREAM_END_TOKEN { + parser.stream_end_produced = true + } + return true +} + +// Set the scanner error and return false. +func yaml_parser_set_scanner_error(parser *yaml_parser_t, context string, context_mark yaml_mark_t, problem string) bool { + parser.error = yaml_SCANNER_ERROR + parser.context = context + parser.context_mark = context_mark + parser.problem = problem + parser.problem_mark = parser.mark + return false +} + +func yaml_parser_set_scanner_tag_error(parser *yaml_parser_t, directive bool, context_mark yaml_mark_t, problem string) bool { + context := "while parsing a tag" + if directive { + context = "while parsing a %TAG directive" + } + return yaml_parser_set_scanner_error(parser, context, context_mark, problem) +} + +func trace(args ...interface{}) func() { + pargs := append([]interface{}{"+++"}, args...) + fmt.Println(pargs...) + pargs = append([]interface{}{"---"}, args...) + return func() { fmt.Println(pargs...) } +} + +// Ensure that the tokens queue contains at least one token which can be +// returned to the Parser. +func yaml_parser_fetch_more_tokens(parser *yaml_parser_t) bool { + // While we need more tokens to fetch, do it. + for { + // [Go] The comment parsing logic requires a lookahead of two tokens + // so that foot comments may be parsed in time of associating them + // with the tokens that are parsed before them, and also for line + // comments to be transformed into head comments in some edge cases. + if parser.tokens_head < len(parser.tokens)-2 { + // If a potential simple key is at the head position, we need to fetch + // the next token to disambiguate it. + head_tok_idx, ok := parser.simple_keys_by_tok[parser.tokens_parsed] + if !ok { + break + } else if valid, ok := yaml_simple_key_is_valid(parser, &parser.simple_keys[head_tok_idx]); !ok { + return false + } else if !valid { + break + } + } + // Fetch the next token. + if !yaml_parser_fetch_next_token(parser) { + return false + } + } + + parser.token_available = true + return true +} + +// The dispatcher for token fetchers. +func yaml_parser_fetch_next_token(parser *yaml_parser_t) (ok bool) { + // Ensure that the buffer is initialized. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // Check if we just started scanning. Fetch STREAM-START then. + if !parser.stream_start_produced { + return yaml_parser_fetch_stream_start(parser) + } + + scan_mark := parser.mark + + // Eat whitespaces and comments until we reach the next token. + if !yaml_parser_scan_to_next_token(parser) { + return false + } + + // [Go] While unrolling indents, transform the head comments of prior + // indentation levels observed after scan_start into foot comments at + // the respective indexes. + + // Check the indentation level against the current column. + if !yaml_parser_unroll_indent(parser, parser.mark.column, scan_mark) { + return false + } + + // Ensure that the buffer contains at least 4 characters. 4 is the length + // of the longest indicators ('--- ' and '... '). + if parser.unread < 4 && !yaml_parser_update_buffer(parser, 4) { + return false + } + + // Is it the end of the stream? + if is_z(parser.buffer, parser.buffer_pos) { + return yaml_parser_fetch_stream_end(parser) + } + + // Is it a directive? + if parser.mark.column == 0 && parser.buffer[parser.buffer_pos] == '%' { + return yaml_parser_fetch_directive(parser) + } + + buf := parser.buffer + pos := parser.buffer_pos + + // Is it the document start indicator? + if parser.mark.column == 0 && buf[pos] == '-' && buf[pos+1] == '-' && buf[pos+2] == '-' && is_blankz(buf, pos+3) { + return yaml_parser_fetch_document_indicator(parser, yaml_DOCUMENT_START_TOKEN) + } + + // Is it the document end indicator? + if parser.mark.column == 0 && buf[pos] == '.' && buf[pos+1] == '.' && buf[pos+2] == '.' && is_blankz(buf, pos+3) { + return yaml_parser_fetch_document_indicator(parser, yaml_DOCUMENT_END_TOKEN) + } + + comment_mark := parser.mark + if len(parser.tokens) > 0 && (parser.flow_level == 0 && buf[pos] == ':' || parser.flow_level > 0 && buf[pos] == ',') { + // Associate any following comments with the prior token. + comment_mark = parser.tokens[len(parser.tokens)-1].start_mark + } + defer func() { + if !ok { + return + } + if len(parser.tokens) > 0 && parser.tokens[len(parser.tokens)-1].typ == yaml_BLOCK_ENTRY_TOKEN { + // Sequence indicators alone have no line comments. It becomes + // a head comment for whatever follows. + return + } + if !yaml_parser_scan_line_comment(parser, comment_mark) { + ok = false + return + } + }() + + // Is it the flow sequence start indicator? + if buf[pos] == '[' { + return yaml_parser_fetch_flow_collection_start(parser, yaml_FLOW_SEQUENCE_START_TOKEN) + } + + // Is it the flow mapping start indicator? + if parser.buffer[parser.buffer_pos] == '{' { + return yaml_parser_fetch_flow_collection_start(parser, yaml_FLOW_MAPPING_START_TOKEN) + } + + // Is it the flow sequence end indicator? + if parser.buffer[parser.buffer_pos] == ']' { + return yaml_parser_fetch_flow_collection_end(parser, + yaml_FLOW_SEQUENCE_END_TOKEN) + } + + // Is it the flow mapping end indicator? + if parser.buffer[parser.buffer_pos] == '}' { + return yaml_parser_fetch_flow_collection_end(parser, + yaml_FLOW_MAPPING_END_TOKEN) + } + + // Is it the flow entry indicator? + if parser.buffer[parser.buffer_pos] == ',' { + return yaml_parser_fetch_flow_entry(parser) + } + + // Is it the block entry indicator? + if parser.buffer[parser.buffer_pos] == '-' && is_blankz(parser.buffer, parser.buffer_pos+1) { + return yaml_parser_fetch_block_entry(parser) + } + + // Is it the key indicator? + if parser.buffer[parser.buffer_pos] == '?' && (parser.flow_level > 0 || is_blankz(parser.buffer, parser.buffer_pos+1)) { + return yaml_parser_fetch_key(parser) + } + + // Is it the value indicator? + if parser.buffer[parser.buffer_pos] == ':' && (parser.flow_level > 0 || is_blankz(parser.buffer, parser.buffer_pos+1)) { + return yaml_parser_fetch_value(parser) + } + + // Is it an alias? + if parser.buffer[parser.buffer_pos] == '*' { + return yaml_parser_fetch_anchor(parser, yaml_ALIAS_TOKEN) + } + + // Is it an anchor? + if parser.buffer[parser.buffer_pos] == '&' { + return yaml_parser_fetch_anchor(parser, yaml_ANCHOR_TOKEN) + } + + // Is it a tag? + if parser.buffer[parser.buffer_pos] == '!' { + return yaml_parser_fetch_tag(parser) + } + + // Is it a literal scalar? + if parser.buffer[parser.buffer_pos] == '|' && parser.flow_level == 0 { + return yaml_parser_fetch_block_scalar(parser, true) + } + + // Is it a folded scalar? + if parser.buffer[parser.buffer_pos] == '>' && parser.flow_level == 0 { + return yaml_parser_fetch_block_scalar(parser, false) + } + + // Is it a single-quoted scalar? + if parser.buffer[parser.buffer_pos] == '\'' { + return yaml_parser_fetch_flow_scalar(parser, true) + } + + // Is it a double-quoted scalar? + if parser.buffer[parser.buffer_pos] == '"' { + return yaml_parser_fetch_flow_scalar(parser, false) + } + + // Is it a plain scalar? + // + // A plain scalar may start with any non-blank characters except + // + // '-', '?', ':', ',', '[', ']', '{', '}', + // '#', '&', '*', '!', '|', '>', '\'', '\"', + // '%', '@', '`'. + // + // In the block context (and, for the '-' indicator, in the flow context + // too), it may also start with the characters + // + // '-', '?', ':' + // + // if it is followed by a non-space character. + // + // The last rule is more restrictive than the specification requires. + // [Go] TODO Make this logic more reasonable. + //switch parser.buffer[parser.buffer_pos] { + //case '-', '?', ':', ',', '?', '-', ',', ':', ']', '[', '}', '{', '&', '#', '!', '*', '>', '|', '"', '\'', '@', '%', '-', '`': + //} + if !(is_blankz(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == '-' || + parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == ':' || + parser.buffer[parser.buffer_pos] == ',' || parser.buffer[parser.buffer_pos] == '[' || + parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '{' || + parser.buffer[parser.buffer_pos] == '}' || parser.buffer[parser.buffer_pos] == '#' || + parser.buffer[parser.buffer_pos] == '&' || parser.buffer[parser.buffer_pos] == '*' || + parser.buffer[parser.buffer_pos] == '!' || parser.buffer[parser.buffer_pos] == '|' || + parser.buffer[parser.buffer_pos] == '>' || parser.buffer[parser.buffer_pos] == '\'' || + parser.buffer[parser.buffer_pos] == '"' || parser.buffer[parser.buffer_pos] == '%' || + parser.buffer[parser.buffer_pos] == '@' || parser.buffer[parser.buffer_pos] == '`') || + (parser.buffer[parser.buffer_pos] == '-' && !is_blank(parser.buffer, parser.buffer_pos+1)) || + (parser.flow_level == 0 && + (parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == ':') && + !is_blankz(parser.buffer, parser.buffer_pos+1)) { + return yaml_parser_fetch_plain_scalar(parser) + } + + // If we don't determine the token type so far, it is an error. + return yaml_parser_set_scanner_error(parser, + "while scanning for the next token", parser.mark, + "found character that cannot start any token") +} + +func yaml_simple_key_is_valid(parser *yaml_parser_t, simple_key *yaml_simple_key_t) (valid, ok bool) { + if !simple_key.possible { + return false, true + } + + // The 1.2 specification says: + // + // "If the ? indicator is omitted, parsing needs to see past the + // implicit key to recognize it as such. To limit the amount of + // lookahead required, the “:” indicator must appear at most 1024 + // Unicode characters beyond the start of the key. In addition, the key + // is restricted to a single line." + // + if simple_key.mark.line < parser.mark.line || simple_key.mark.index+1024 < parser.mark.index { + // Check if the potential simple key to be removed is required. + if simple_key.required { + return false, yaml_parser_set_scanner_error(parser, + "while scanning a simple key", simple_key.mark, + "could not find expected ':'") + } + simple_key.possible = false + return false, true + } + return true, true +} + +// Check if a simple key may start at the current position and add it if +// needed. +func yaml_parser_save_simple_key(parser *yaml_parser_t) bool { + // A simple key is required at the current position if the scanner is in + // the block context and the current column coincides with the indentation + // level. + + required := parser.flow_level == 0 && parser.indent == parser.mark.column + + // + // If the current position may start a simple key, save it. + // + if parser.simple_key_allowed { + simple_key := yaml_simple_key_t{ + possible: true, + required: required, + token_number: parser.tokens_parsed + (len(parser.tokens) - parser.tokens_head), + mark: parser.mark, + } + + if !yaml_parser_remove_simple_key(parser) { + return false + } + parser.simple_keys[len(parser.simple_keys)-1] = simple_key + parser.simple_keys_by_tok[simple_key.token_number] = len(parser.simple_keys) - 1 + } + return true +} + +// Remove a potential simple key at the current flow level. +func yaml_parser_remove_simple_key(parser *yaml_parser_t) bool { + i := len(parser.simple_keys) - 1 + if parser.simple_keys[i].possible { + // If the key is required, it is an error. + if parser.simple_keys[i].required { + return yaml_parser_set_scanner_error(parser, + "while scanning a simple key", parser.simple_keys[i].mark, + "could not find expected ':'") + } + // Remove the key from the stack. + parser.simple_keys[i].possible = false + delete(parser.simple_keys_by_tok, parser.simple_keys[i].token_number) + } + return true +} + +// max_flow_level limits the flow_level +const max_flow_level = 10000 + +// Increase the flow level and resize the simple key list if needed. +func yaml_parser_increase_flow_level(parser *yaml_parser_t) bool { + // Reset the simple key on the next level. + parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{ + possible: false, + required: false, + token_number: parser.tokens_parsed + (len(parser.tokens) - parser.tokens_head), + mark: parser.mark, + }) + + // Increase the flow level. + parser.flow_level++ + if parser.flow_level > max_flow_level { + return yaml_parser_set_scanner_error(parser, + "while increasing flow level", parser.simple_keys[len(parser.simple_keys)-1].mark, + fmt.Sprintf("exceeded max depth of %d", max_flow_level)) + } + return true +} + +// Decrease the flow level. +func yaml_parser_decrease_flow_level(parser *yaml_parser_t) bool { + if parser.flow_level > 0 { + parser.flow_level-- + last := len(parser.simple_keys) - 1 + delete(parser.simple_keys_by_tok, parser.simple_keys[last].token_number) + parser.simple_keys = parser.simple_keys[:last] + } + return true +} + +// max_indents limits the indents stack size +const max_indents = 10000 + +// Push the current indentation level to the stack and set the new level +// the current column is greater than the indentation level. In this case, +// append or insert the specified token into the token queue. +func yaml_parser_roll_indent(parser *yaml_parser_t, column, number int, typ yaml_token_type_t, mark yaml_mark_t) bool { + // In the flow context, do nothing. + if parser.flow_level > 0 { + return true + } + + if parser.indent < column { + // Push the current indentation level to the stack and set the new + // indentation level. + parser.indents = append(parser.indents, parser.indent) + parser.indent = column + if len(parser.indents) > max_indents { + return yaml_parser_set_scanner_error(parser, + "while increasing indent level", parser.simple_keys[len(parser.simple_keys)-1].mark, + fmt.Sprintf("exceeded max depth of %d", max_indents)) + } + + // Create a token and insert it into the queue. + token := yaml_token_t{ + typ: typ, + start_mark: mark, + end_mark: mark, + } + if number > -1 { + number -= parser.tokens_parsed + } + yaml_insert_token(parser, number, &token) + } + return true +} + +// Pop indentation levels from the indents stack until the current level +// becomes less or equal to the column. For each indentation level, append +// the BLOCK-END token. +func yaml_parser_unroll_indent(parser *yaml_parser_t, column int, scan_mark yaml_mark_t) bool { + // In the flow context, do nothing. + if parser.flow_level > 0 { + return true + } + + block_mark := scan_mark + block_mark.index-- + + // Loop through the indentation levels in the stack. + for parser.indent > column { + + // [Go] Reposition the end token before potential following + // foot comments of parent blocks. For that, search + // backwards for recent comments that were at the same + // indent as the block that is ending now. + stop_index := block_mark.index + for i := len(parser.comments) - 1; i >= 0; i-- { + comment := &parser.comments[i] + + if comment.end_mark.index < stop_index { + // Don't go back beyond the start of the comment/whitespace scan, unless column < 0. + // If requested indent column is < 0, then the document is over and everything else + // is a foot anyway. + break + } + if comment.start_mark.column == parser.indent+1 { + // This is a good match. But maybe there's a former comment + // at that same indent level, so keep searching. + block_mark = comment.start_mark + } + + // While the end of the former comment matches with + // the start of the following one, we know there's + // nothing in between and scanning is still safe. + stop_index = comment.scan_mark.index + } + + // Create a token and append it to the queue. + token := yaml_token_t{ + typ: yaml_BLOCK_END_TOKEN, + start_mark: block_mark, + end_mark: block_mark, + } + yaml_insert_token(parser, -1, &token) + + // Pop the indentation level. + parser.indent = parser.indents[len(parser.indents)-1] + parser.indents = parser.indents[:len(parser.indents)-1] + } + return true +} + +// Initialize the scanner and produce the STREAM-START token. +func yaml_parser_fetch_stream_start(parser *yaml_parser_t) bool { + + // Set the initial indentation. + parser.indent = -1 + + // Initialize the simple key stack. + parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{}) + + parser.simple_keys_by_tok = make(map[int]int) + + // A simple key is allowed at the beginning of the stream. + parser.simple_key_allowed = true + + // We have started. + parser.stream_start_produced = true + + // Create the STREAM-START token and append it to the queue. + token := yaml_token_t{ + typ: yaml_STREAM_START_TOKEN, + start_mark: parser.mark, + end_mark: parser.mark, + encoding: parser.encoding, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the STREAM-END token and shut down the scanner. +func yaml_parser_fetch_stream_end(parser *yaml_parser_t) bool { + + // Force new line. + if parser.mark.column != 0 { + parser.mark.column = 0 + parser.mark.line++ + } + + // Reset the indentation level. + if !yaml_parser_unroll_indent(parser, -1, parser.mark) { + return false + } + + // Reset simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + parser.simple_key_allowed = false + + // Create the STREAM-END token and append it to the queue. + token := yaml_token_t{ + typ: yaml_STREAM_END_TOKEN, + start_mark: parser.mark, + end_mark: parser.mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce a VERSION-DIRECTIVE or TAG-DIRECTIVE token. +func yaml_parser_fetch_directive(parser *yaml_parser_t) bool { + // Reset the indentation level. + if !yaml_parser_unroll_indent(parser, -1, parser.mark) { + return false + } + + // Reset simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + parser.simple_key_allowed = false + + // Create the YAML-DIRECTIVE or TAG-DIRECTIVE token. + token := yaml_token_t{} + if !yaml_parser_scan_directive(parser, &token) { + return false + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the DOCUMENT-START or DOCUMENT-END token. +func yaml_parser_fetch_document_indicator(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // Reset the indentation level. + if !yaml_parser_unroll_indent(parser, -1, parser.mark) { + return false + } + + // Reset simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + parser.simple_key_allowed = false + + // Consume the token. + start_mark := parser.mark + + skip(parser) + skip(parser) + skip(parser) + + end_mark := parser.mark + + // Create the DOCUMENT-START or DOCUMENT-END token. + token := yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the FLOW-SEQUENCE-START or FLOW-MAPPING-START token. +func yaml_parser_fetch_flow_collection_start(parser *yaml_parser_t, typ yaml_token_type_t) bool { + + // The indicators '[' and '{' may start a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // Increase the flow level. + if !yaml_parser_increase_flow_level(parser) { + return false + } + + // A simple key may follow the indicators '[' and '{'. + parser.simple_key_allowed = true + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the FLOW-SEQUENCE-START of FLOW-MAPPING-START token. + token := yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the FLOW-SEQUENCE-END or FLOW-MAPPING-END token. +func yaml_parser_fetch_flow_collection_end(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // Reset any potential simple key on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Decrease the flow level. + if !yaml_parser_decrease_flow_level(parser) { + return false + } + + // No simple keys after the indicators ']' and '}'. + parser.simple_key_allowed = false + + // Consume the token. + + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the FLOW-SEQUENCE-END of FLOW-MAPPING-END token. + token := yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the FLOW-ENTRY token. +func yaml_parser_fetch_flow_entry(parser *yaml_parser_t) bool { + // Reset any potential simple keys on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Simple keys are allowed after ','. + parser.simple_key_allowed = true + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the FLOW-ENTRY token and append it to the queue. + token := yaml_token_t{ + typ: yaml_FLOW_ENTRY_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the BLOCK-ENTRY token. +func yaml_parser_fetch_block_entry(parser *yaml_parser_t) bool { + // Check if the scanner is in the block context. + if parser.flow_level == 0 { + // Check if we are allowed to start a new entry. + if !parser.simple_key_allowed { + return yaml_parser_set_scanner_error(parser, "", parser.mark, + "block sequence entries are not allowed in this context") + } + // Add the BLOCK-SEQUENCE-START token if needed. + if !yaml_parser_roll_indent(parser, parser.mark.column, -1, yaml_BLOCK_SEQUENCE_START_TOKEN, parser.mark) { + return false + } + } else { + // It is an error for the '-' indicator to occur in the flow context, + // but we let the Parser detect and report about it because the Parser + // is able to point to the context. + } + + // Reset any potential simple keys on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Simple keys are allowed after '-'. + parser.simple_key_allowed = true + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the BLOCK-ENTRY token and append it to the queue. + token := yaml_token_t{ + typ: yaml_BLOCK_ENTRY_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the KEY token. +func yaml_parser_fetch_key(parser *yaml_parser_t) bool { + + // In the block context, additional checks are required. + if parser.flow_level == 0 { + // Check if we are allowed to start a new key (not nessesary simple). + if !parser.simple_key_allowed { + return yaml_parser_set_scanner_error(parser, "", parser.mark, + "mapping keys are not allowed in this context") + } + // Add the BLOCK-MAPPING-START token if needed. + if !yaml_parser_roll_indent(parser, parser.mark.column, -1, yaml_BLOCK_MAPPING_START_TOKEN, parser.mark) { + return false + } + } + + // Reset any potential simple keys on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Simple keys are allowed after '?' in the block context. + parser.simple_key_allowed = parser.flow_level == 0 + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the KEY token and append it to the queue. + token := yaml_token_t{ + typ: yaml_KEY_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the VALUE token. +func yaml_parser_fetch_value(parser *yaml_parser_t) bool { + + simple_key := &parser.simple_keys[len(parser.simple_keys)-1] + + // Have we found a simple key? + if valid, ok := yaml_simple_key_is_valid(parser, simple_key); !ok { + return false + + } else if valid { + + // Create the KEY token and insert it into the queue. + token := yaml_token_t{ + typ: yaml_KEY_TOKEN, + start_mark: simple_key.mark, + end_mark: simple_key.mark, + } + yaml_insert_token(parser, simple_key.token_number-parser.tokens_parsed, &token) + + // In the block context, we may need to add the BLOCK-MAPPING-START token. + if !yaml_parser_roll_indent(parser, simple_key.mark.column, + simple_key.token_number, + yaml_BLOCK_MAPPING_START_TOKEN, simple_key.mark) { + return false + } + + // Remove the simple key. + simple_key.possible = false + delete(parser.simple_keys_by_tok, simple_key.token_number) + + // A simple key cannot follow another simple key. + parser.simple_key_allowed = false + + } else { + // The ':' indicator follows a complex key. + + // In the block context, extra checks are required. + if parser.flow_level == 0 { + + // Check if we are allowed to start a complex value. + if !parser.simple_key_allowed { + return yaml_parser_set_scanner_error(parser, "", parser.mark, + "mapping values are not allowed in this context") + } + + // Add the BLOCK-MAPPING-START token if needed. + if !yaml_parser_roll_indent(parser, parser.mark.column, -1, yaml_BLOCK_MAPPING_START_TOKEN, parser.mark) { + return false + } + } + + // Simple keys after ':' are allowed in the block context. + parser.simple_key_allowed = parser.flow_level == 0 + } + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the VALUE token and append it to the queue. + token := yaml_token_t{ + typ: yaml_VALUE_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the ALIAS or ANCHOR token. +func yaml_parser_fetch_anchor(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // An anchor or an alias could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow an anchor or an alias. + parser.simple_key_allowed = false + + // Create the ALIAS or ANCHOR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_anchor(parser, &token, typ) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the TAG token. +func yaml_parser_fetch_tag(parser *yaml_parser_t) bool { + // A tag could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow a tag. + parser.simple_key_allowed = false + + // Create the TAG token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_tag(parser, &token) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the SCALAR(...,literal) or SCALAR(...,folded) tokens. +func yaml_parser_fetch_block_scalar(parser *yaml_parser_t, literal bool) bool { + // Remove any potential simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // A simple key may follow a block scalar. + parser.simple_key_allowed = true + + // Create the SCALAR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_block_scalar(parser, &token, literal) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the SCALAR(...,single-quoted) or SCALAR(...,double-quoted) tokens. +func yaml_parser_fetch_flow_scalar(parser *yaml_parser_t, single bool) bool { + // A plain scalar could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow a flow scalar. + parser.simple_key_allowed = false + + // Create the SCALAR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_flow_scalar(parser, &token, single) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the SCALAR(...,plain) token. +func yaml_parser_fetch_plain_scalar(parser *yaml_parser_t) bool { + // A plain scalar could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow a flow scalar. + parser.simple_key_allowed = false + + // Create the SCALAR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_plain_scalar(parser, &token) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Eat whitespaces and comments until the next token is found. +func yaml_parser_scan_to_next_token(parser *yaml_parser_t) bool { + + scan_mark := parser.mark + + // Until the next token is not found. + for { + // Allow the BOM mark to start a line. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if parser.mark.column == 0 && is_bom(parser.buffer, parser.buffer_pos) { + skip(parser) + } + + // Eat whitespaces. + // Tabs are allowed: + // - in the flow context + // - in the block context, but not at the beginning of the line or + // after '-', '?', or ':' (complex value). + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for parser.buffer[parser.buffer_pos] == ' ' || ((parser.flow_level > 0 || !parser.simple_key_allowed) && parser.buffer[parser.buffer_pos] == '\t') { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check if we just had a line comment under a sequence entry that + // looks more like a header to the following content. Similar to this: + // + // - # The comment + // - Some data + // + // If so, transform the line comment to a head comment and reposition. + if len(parser.comments) > 0 && len(parser.tokens) > 1 { + tokenA := parser.tokens[len(parser.tokens)-2] + tokenB := parser.tokens[len(parser.tokens)-1] + comment := &parser.comments[len(parser.comments)-1] + if tokenA.typ == yaml_BLOCK_SEQUENCE_START_TOKEN && tokenB.typ == yaml_BLOCK_ENTRY_TOKEN && len(comment.line) > 0 && !is_break(parser.buffer, parser.buffer_pos) { + // If it was in the prior line, reposition so it becomes a + // header of the follow up token. Otherwise, keep it in place + // so it becomes a header of the former. + comment.head = comment.line + comment.line = nil + if comment.start_mark.line == parser.mark.line-1 { + comment.token_mark = parser.mark + } + } + } + + // Eat a comment until a line break. + if parser.buffer[parser.buffer_pos] == '#' { + if !yaml_parser_scan_comments(parser, scan_mark) { + return false + } + } + + // If it is a line break, eat it. + if is_break(parser.buffer, parser.buffer_pos) { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + + // In the block context, a new line may start a simple key. + if parser.flow_level == 0 { + parser.simple_key_allowed = true + } + } else { + break // We have found a token. + } + } + + return true +} + +// Scan a YAML-DIRECTIVE or TAG-DIRECTIVE token. +// +// Scope: +// +// %YAML 1.1 # a comment \n +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// %TAG !yaml! tag:yaml.org,2002: \n +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +func yaml_parser_scan_directive(parser *yaml_parser_t, token *yaml_token_t) bool { + // Eat '%'. + start_mark := parser.mark + skip(parser) + + // Scan the directive name. + var name []byte + if !yaml_parser_scan_directive_name(parser, start_mark, &name) { + return false + } + + // Is it a YAML directive? + if bytes.Equal(name, []byte("YAML")) { + // Scan the VERSION directive value. + var major, minor int8 + if !yaml_parser_scan_version_directive_value(parser, start_mark, &major, &minor) { + return false + } + end_mark := parser.mark + + // Create a VERSION-DIRECTIVE token. + *token = yaml_token_t{ + typ: yaml_VERSION_DIRECTIVE_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + major: major, + minor: minor, + } + + // Is it a TAG directive? + } else if bytes.Equal(name, []byte("TAG")) { + // Scan the TAG directive value. + var handle, prefix []byte + if !yaml_parser_scan_tag_directive_value(parser, start_mark, &handle, &prefix) { + return false + } + end_mark := parser.mark + + // Create a TAG-DIRECTIVE token. + *token = yaml_token_t{ + typ: yaml_TAG_DIRECTIVE_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: handle, + prefix: prefix, + } + + // Unknown directive. + } else { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "found unknown directive name") + return false + } + + // Eat the rest of the line including any comments. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + if parser.buffer[parser.buffer_pos] == '#' { + // [Go] Discard this inline comment for the time being. + //if !yaml_parser_scan_line_comment(parser, start_mark) { + // return false + //} + for !is_breakz(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + } + + // Check if we are at the end of the line. + if !is_breakz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "did not find expected comment or line break") + return false + } + + // Eat a line break. + if is_break(parser.buffer, parser.buffer_pos) { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + } + + return true +} + +// Scan the directive name. +// +// Scope: +// +// %YAML 1.1 # a comment \n +// ^^^^ +// %TAG !yaml! tag:yaml.org,2002: \n +// ^^^ +func yaml_parser_scan_directive_name(parser *yaml_parser_t, start_mark yaml_mark_t, name *[]byte) bool { + // Consume the directive name. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + var s []byte + for is_alpha(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check if the name is empty. + if len(s) == 0 { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "could not find expected directive name") + return false + } + + // Check for an blank character after the name. + if !is_blankz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "found unexpected non-alphabetical character") + return false + } + *name = s + return true +} + +// Scan the value of VERSION-DIRECTIVE. +// +// Scope: +// +// %YAML 1.1 # a comment \n +// ^^^^^^ +func yaml_parser_scan_version_directive_value(parser *yaml_parser_t, start_mark yaml_mark_t, major, minor *int8) bool { + // Eat whitespaces. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Consume the major version number. + if !yaml_parser_scan_version_directive_number(parser, start_mark, major) { + return false + } + + // Eat '.'. + if parser.buffer[parser.buffer_pos] != '.' { + return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", + start_mark, "did not find expected digit or '.' character") + } + + skip(parser) + + // Consume the minor version number. + if !yaml_parser_scan_version_directive_number(parser, start_mark, minor) { + return false + } + return true +} + +const max_number_length = 2 + +// Scan the version number of VERSION-DIRECTIVE. +// +// Scope: +// +// %YAML 1.1 # a comment \n +// ^ +// %YAML 1.1 # a comment \n +// ^ +func yaml_parser_scan_version_directive_number(parser *yaml_parser_t, start_mark yaml_mark_t, number *int8) bool { + + // Repeat while the next character is digit. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + var value, length int8 + for is_digit(parser.buffer, parser.buffer_pos) { + // Check if the number is too long. + length++ + if length > max_number_length { + return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", + start_mark, "found extremely long version number") + } + value = value*10 + int8(as_digit(parser.buffer, parser.buffer_pos)) + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check if the number was present. + if length == 0 { + return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", + start_mark, "did not find expected version number") + } + *number = value + return true +} + +// Scan the value of a TAG-DIRECTIVE token. +// +// Scope: +// +// %TAG !yaml! tag:yaml.org,2002: \n +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +func yaml_parser_scan_tag_directive_value(parser *yaml_parser_t, start_mark yaml_mark_t, handle, prefix *[]byte) bool { + var handle_value, prefix_value []byte + + // Eat whitespaces. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Scan a handle. + if !yaml_parser_scan_tag_handle(parser, true, start_mark, &handle_value) { + return false + } + + // Expect a whitespace. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if !is_blank(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a %TAG directive", + start_mark, "did not find expected whitespace") + return false + } + + // Eat whitespaces. + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Scan a prefix. + if !yaml_parser_scan_tag_uri(parser, true, nil, start_mark, &prefix_value) { + return false + } + + // Expect a whitespace or line break. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if !is_blankz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a %TAG directive", + start_mark, "did not find expected whitespace or line break") + return false + } + + *handle = handle_value + *prefix = prefix_value + return true +} + +func yaml_parser_scan_anchor(parser *yaml_parser_t, token *yaml_token_t, typ yaml_token_type_t) bool { + var s []byte + + // Eat the indicator character. + start_mark := parser.mark + skip(parser) + + // Consume the value. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_alpha(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + end_mark := parser.mark + + /* + * Check if length of the anchor is greater than 0 and it is followed by + * a whitespace character or one of the indicators: + * + * '?', ':', ',', ']', '}', '%', '@', '`'. + */ + + if len(s) == 0 || + !(is_blankz(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == '?' || + parser.buffer[parser.buffer_pos] == ':' || parser.buffer[parser.buffer_pos] == ',' || + parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '}' || + parser.buffer[parser.buffer_pos] == '%' || parser.buffer[parser.buffer_pos] == '@' || + parser.buffer[parser.buffer_pos] == '`') { + context := "while scanning an alias" + if typ == yaml_ANCHOR_TOKEN { + context = "while scanning an anchor" + } + yaml_parser_set_scanner_error(parser, context, start_mark, + "did not find expected alphabetic or numeric character") + return false + } + + // Create a token. + *token = yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + value: s, + } + + return true +} + +/* + * Scan a TAG token. + */ + +func yaml_parser_scan_tag(parser *yaml_parser_t, token *yaml_token_t) bool { + var handle, suffix []byte + + start_mark := parser.mark + + // Check if the tag is in the canonical form. + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + if parser.buffer[parser.buffer_pos+1] == '<' { + // Keep the handle as '' + + // Eat '!<' + skip(parser) + skip(parser) + + // Consume the tag value. + if !yaml_parser_scan_tag_uri(parser, false, nil, start_mark, &suffix) { + return false + } + + // Check for '>' and eat it. + if parser.buffer[parser.buffer_pos] != '>' { + yaml_parser_set_scanner_error(parser, "while scanning a tag", + start_mark, "did not find the expected '>'") + return false + } + + skip(parser) + } else { + // The tag has either the '!suffix' or the '!handle!suffix' form. + + // First, try to scan a handle. + if !yaml_parser_scan_tag_handle(parser, false, start_mark, &handle) { + return false + } + + // Check if it is, indeed, handle. + if handle[0] == '!' && len(handle) > 1 && handle[len(handle)-1] == '!' { + // Scan the suffix now. + if !yaml_parser_scan_tag_uri(parser, false, nil, start_mark, &suffix) { + return false + } + } else { + // It wasn't a handle after all. Scan the rest of the tag. + if !yaml_parser_scan_tag_uri(parser, false, handle, start_mark, &suffix) { + return false + } + + // Set the handle to '!'. + handle = []byte{'!'} + + // A special case: the '!' tag. Set the handle to '' and the + // suffix to '!'. + if len(suffix) == 0 { + handle, suffix = suffix, handle + } + } + } + + // Check the character which ends the tag. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if !is_blankz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a tag", + start_mark, "did not find expected whitespace or line break") + return false + } + + end_mark := parser.mark + + // Create a token. + *token = yaml_token_t{ + typ: yaml_TAG_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: handle, + suffix: suffix, + } + return true +} + +// Scan a tag handle. +func yaml_parser_scan_tag_handle(parser *yaml_parser_t, directive bool, start_mark yaml_mark_t, handle *[]byte) bool { + // Check the initial '!' character. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if parser.buffer[parser.buffer_pos] != '!' { + yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find expected '!'") + return false + } + + var s []byte + + // Copy the '!' character. + s = read(parser, s) + + // Copy all subsequent alphabetical and numerical characters. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for is_alpha(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check if the trailing character is '!' and copy it. + if parser.buffer[parser.buffer_pos] == '!' { + s = read(parser, s) + } else { + // It's either the '!' tag or not really a tag handle. If it's a %TAG + // directive, it's an error. If it's a tag token, it must be a part of URI. + if directive && string(s) != "!" { + yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find expected '!'") + return false + } + } + + *handle = s + return true +} + +// Scan a tag. +func yaml_parser_scan_tag_uri(parser *yaml_parser_t, directive bool, head []byte, start_mark yaml_mark_t, uri *[]byte) bool { + //size_t length = head ? strlen((char *)head) : 0 + var s []byte + hasTag := len(head) > 0 + + // Copy the head if needed. + // + // Note that we don't copy the leading '!' character. + if len(head) > 1 { + s = append(s, head[1:]...) + } + + // Scan the tag. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // The set of characters that may appear in URI is as follows: + // + // '0'-'9', 'A'-'Z', 'a'-'z', '_', '-', ';', '/', '?', ':', '@', '&', + // '=', '+', '$', ',', '.', '!', '~', '*', '\'', '(', ')', '[', ']', + // '%'. + // [Go] TODO Convert this into more reasonable logic. + for is_alpha(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == ';' || + parser.buffer[parser.buffer_pos] == '/' || parser.buffer[parser.buffer_pos] == '?' || + parser.buffer[parser.buffer_pos] == ':' || parser.buffer[parser.buffer_pos] == '@' || + parser.buffer[parser.buffer_pos] == '&' || parser.buffer[parser.buffer_pos] == '=' || + parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '$' || + parser.buffer[parser.buffer_pos] == ',' || parser.buffer[parser.buffer_pos] == '.' || + parser.buffer[parser.buffer_pos] == '!' || parser.buffer[parser.buffer_pos] == '~' || + parser.buffer[parser.buffer_pos] == '*' || parser.buffer[parser.buffer_pos] == '\'' || + parser.buffer[parser.buffer_pos] == '(' || parser.buffer[parser.buffer_pos] == ')' || + parser.buffer[parser.buffer_pos] == '[' || parser.buffer[parser.buffer_pos] == ']' || + parser.buffer[parser.buffer_pos] == '%' { + // Check if it is a URI-escape sequence. + if parser.buffer[parser.buffer_pos] == '%' { + if !yaml_parser_scan_uri_escapes(parser, directive, start_mark, &s) { + return false + } + } else { + s = read(parser, s) + } + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + hasTag = true + } + + if !hasTag { + yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find expected tag URI") + return false + } + *uri = s + return true +} + +// Decode an URI-escape sequence corresponding to a single UTF-8 character. +func yaml_parser_scan_uri_escapes(parser *yaml_parser_t, directive bool, start_mark yaml_mark_t, s *[]byte) bool { + + // Decode the required number of characters. + w := 1024 + for w > 0 { + // Check for a URI-escaped octet. + if parser.unread < 3 && !yaml_parser_update_buffer(parser, 3) { + return false + } + + if !(parser.buffer[parser.buffer_pos] == '%' && + is_hex(parser.buffer, parser.buffer_pos+1) && + is_hex(parser.buffer, parser.buffer_pos+2)) { + return yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find URI escaped octet") + } + + // Get the octet. + octet := byte((as_hex(parser.buffer, parser.buffer_pos+1) << 4) + as_hex(parser.buffer, parser.buffer_pos+2)) + + // If it is the leading octet, determine the length of the UTF-8 sequence. + if w == 1024 { + w = width(octet) + if w == 0 { + return yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "found an incorrect leading UTF-8 octet") + } + } else { + // Check if the trailing octet is correct. + if octet&0xC0 != 0x80 { + return yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "found an incorrect trailing UTF-8 octet") + } + } + + // Copy the octet and move the pointers. + *s = append(*s, octet) + skip(parser) + skip(parser) + skip(parser) + w-- + } + return true +} + +// Scan a block scalar. +func yaml_parser_scan_block_scalar(parser *yaml_parser_t, token *yaml_token_t, literal bool) bool { + // Eat the indicator '|' or '>'. + start_mark := parser.mark + skip(parser) + + // Scan the additional block scalar indicators. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // Check for a chomping indicator. + var chomping, increment int + if parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '-' { + // Set the chomping method and eat the indicator. + if parser.buffer[parser.buffer_pos] == '+' { + chomping = +1 + } else { + chomping = -1 + } + skip(parser) + + // Check for an indentation indicator. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if is_digit(parser.buffer, parser.buffer_pos) { + // Check that the indentation is greater than 0. + if parser.buffer[parser.buffer_pos] == '0' { + yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "found an indentation indicator equal to 0") + return false + } + + // Get the indentation level and eat the indicator. + increment = as_digit(parser.buffer, parser.buffer_pos) + skip(parser) + } + + } else if is_digit(parser.buffer, parser.buffer_pos) { + // Do the same as above, but in the opposite order. + + if parser.buffer[parser.buffer_pos] == '0' { + yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "found an indentation indicator equal to 0") + return false + } + increment = as_digit(parser.buffer, parser.buffer_pos) + skip(parser) + + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '-' { + if parser.buffer[parser.buffer_pos] == '+' { + chomping = +1 + } else { + chomping = -1 + } + skip(parser) + } + } + + // Eat whitespaces and comments to the end of the line. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + if parser.buffer[parser.buffer_pos] == '#' { + if !yaml_parser_scan_line_comment(parser, start_mark) { + return false + } + for !is_breakz(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + } + + // Check if we are at the end of the line. + if !is_breakz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "did not find expected comment or line break") + return false + } + + // Eat a line break. + if is_break(parser.buffer, parser.buffer_pos) { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + } + + end_mark := parser.mark + + // Set the indentation level if it was specified. + var indent int + if increment > 0 { + if parser.indent >= 0 { + indent = parser.indent + increment + } else { + indent = increment + } + } + + // Scan the leading line breaks and determine the indentation level if needed. + var s, leading_break, trailing_breaks []byte + if !yaml_parser_scan_block_scalar_breaks(parser, &indent, &trailing_breaks, start_mark, &end_mark) { + return false + } + + // Scan the block scalar content. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + var leading_blank, trailing_blank bool + for parser.mark.column == indent && !is_z(parser.buffer, parser.buffer_pos) { + // We are at the beginning of a non-empty line. + + // Is it a trailing whitespace? + trailing_blank = is_blank(parser.buffer, parser.buffer_pos) + + // Check if we need to fold the leading line break. + if !literal && !leading_blank && !trailing_blank && len(leading_break) > 0 && leading_break[0] == '\n' { + // Do we need to join the lines by space? + if len(trailing_breaks) == 0 { + s = append(s, ' ') + } + } else { + s = append(s, leading_break...) + } + leading_break = leading_break[:0] + + // Append the remaining line breaks. + s = append(s, trailing_breaks...) + trailing_breaks = trailing_breaks[:0] + + // Is it a leading whitespace? + leading_blank = is_blank(parser.buffer, parser.buffer_pos) + + // Consume the current line. + for !is_breakz(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Consume the line break. + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + leading_break = read_line(parser, leading_break) + + // Eat the following indentation spaces and line breaks. + if !yaml_parser_scan_block_scalar_breaks(parser, &indent, &trailing_breaks, start_mark, &end_mark) { + return false + } + } + + // Chomp the tail. + if chomping != -1 { + s = append(s, leading_break...) + } + if chomping == 1 { + s = append(s, trailing_breaks...) + } + + // Create a token. + *token = yaml_token_t{ + typ: yaml_SCALAR_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: s, + style: yaml_LITERAL_SCALAR_STYLE, + } + if !literal { + token.style = yaml_FOLDED_SCALAR_STYLE + } + return true +} + +// Scan indentation spaces and line breaks for a block scalar. Determine the +// indentation level if needed. +func yaml_parser_scan_block_scalar_breaks(parser *yaml_parser_t, indent *int, breaks *[]byte, start_mark yaml_mark_t, end_mark *yaml_mark_t) bool { + *end_mark = parser.mark + + // Eat the indentation spaces and line breaks. + max_indent := 0 + for { + // Eat the indentation spaces. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for (*indent == 0 || parser.mark.column < *indent) && is_space(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + if parser.mark.column > max_indent { + max_indent = parser.mark.column + } + + // Check for a tab character messing the indentation. + if (*indent == 0 || parser.mark.column < *indent) && is_tab(parser.buffer, parser.buffer_pos) { + return yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "found a tab character where an indentation space is expected") + } + + // Have we found a non-empty line? + if !is_break(parser.buffer, parser.buffer_pos) { + break + } + + // Consume the line break. + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + // [Go] Should really be returning breaks instead. + *breaks = read_line(parser, *breaks) + *end_mark = parser.mark + } + + // Determine the indentation level if needed. + if *indent == 0 { + *indent = max_indent + if *indent < parser.indent+1 { + *indent = parser.indent + 1 + } + if *indent < 1 { + *indent = 1 + } + } + return true +} + +// Scan a quoted scalar. +func yaml_parser_scan_flow_scalar(parser *yaml_parser_t, token *yaml_token_t, single bool) bool { + // Eat the left quote. + start_mark := parser.mark + skip(parser) + + // Consume the content of the quoted scalar. + var s, leading_break, trailing_breaks, whitespaces []byte + for { + // Check that there are no document indicators at the beginning of the line. + if parser.unread < 4 && !yaml_parser_update_buffer(parser, 4) { + return false + } + + if parser.mark.column == 0 && + ((parser.buffer[parser.buffer_pos+0] == '-' && + parser.buffer[parser.buffer_pos+1] == '-' && + parser.buffer[parser.buffer_pos+2] == '-') || + (parser.buffer[parser.buffer_pos+0] == '.' && + parser.buffer[parser.buffer_pos+1] == '.' && + parser.buffer[parser.buffer_pos+2] == '.')) && + is_blankz(parser.buffer, parser.buffer_pos+3) { + yaml_parser_set_scanner_error(parser, "while scanning a quoted scalar", + start_mark, "found unexpected document indicator") + return false + } + + // Check for EOF. + if is_z(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a quoted scalar", + start_mark, "found unexpected end of stream") + return false + } + + // Consume non-blank characters. + leading_blanks := false + for !is_blankz(parser.buffer, parser.buffer_pos) { + if single && parser.buffer[parser.buffer_pos] == '\'' && parser.buffer[parser.buffer_pos+1] == '\'' { + // Is is an escaped single quote. + s = append(s, '\'') + skip(parser) + skip(parser) + + } else if single && parser.buffer[parser.buffer_pos] == '\'' { + // It is a right single quote. + break + } else if !single && parser.buffer[parser.buffer_pos] == '"' { + // It is a right double quote. + break + + } else if !single && parser.buffer[parser.buffer_pos] == '\\' && is_break(parser.buffer, parser.buffer_pos+1) { + // It is an escaped line break. + if parser.unread < 3 && !yaml_parser_update_buffer(parser, 3) { + return false + } + skip(parser) + skip_line(parser) + leading_blanks = true + break + + } else if !single && parser.buffer[parser.buffer_pos] == '\\' { + // It is an escape sequence. + code_length := 0 + + // Check the escape character. + switch parser.buffer[parser.buffer_pos+1] { + case '0': + s = append(s, 0) + case 'a': + s = append(s, '\x07') + case 'b': + s = append(s, '\x08') + case 't', '\t': + s = append(s, '\x09') + case 'n': + s = append(s, '\x0A') + case 'v': + s = append(s, '\x0B') + case 'f': + s = append(s, '\x0C') + case 'r': + s = append(s, '\x0D') + case 'e': + s = append(s, '\x1B') + case ' ': + s = append(s, '\x20') + case '"': + s = append(s, '"') + case '\'': + s = append(s, '\'') + case '\\': + s = append(s, '\\') + case 'N': // NEL (#x85) + s = append(s, '\xC2') + s = append(s, '\x85') + case '_': // #xA0 + s = append(s, '\xC2') + s = append(s, '\xA0') + case 'L': // LS (#x2028) + s = append(s, '\xE2') + s = append(s, '\x80') + s = append(s, '\xA8') + case 'P': // PS (#x2029) + s = append(s, '\xE2') + s = append(s, '\x80') + s = append(s, '\xA9') + case 'x': + code_length = 2 + case 'u': + code_length = 4 + case 'U': + code_length = 8 + default: + yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", + start_mark, "found unknown escape character") + return false + } + + skip(parser) + skip(parser) + + // Consume an arbitrary escape code. + if code_length > 0 { + var value int + + // Scan the character value. + if parser.unread < code_length && !yaml_parser_update_buffer(parser, code_length) { + return false + } + for k := 0; k < code_length; k++ { + if !is_hex(parser.buffer, parser.buffer_pos+k) { + yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", + start_mark, "did not find expected hexdecimal number") + return false + } + value = (value << 4) + as_hex(parser.buffer, parser.buffer_pos+k) + } + + // Check the value and write the character. + if (value >= 0xD800 && value <= 0xDFFF) || value > 0x10FFFF { + yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", + start_mark, "found invalid Unicode character escape code") + return false + } + if value <= 0x7F { + s = append(s, byte(value)) + } else if value <= 0x7FF { + s = append(s, byte(0xC0+(value>>6))) + s = append(s, byte(0x80+(value&0x3F))) + } else if value <= 0xFFFF { + s = append(s, byte(0xE0+(value>>12))) + s = append(s, byte(0x80+((value>>6)&0x3F))) + s = append(s, byte(0x80+(value&0x3F))) + } else { + s = append(s, byte(0xF0+(value>>18))) + s = append(s, byte(0x80+((value>>12)&0x3F))) + s = append(s, byte(0x80+((value>>6)&0x3F))) + s = append(s, byte(0x80+(value&0x3F))) + } + + // Advance the pointer. + for k := 0; k < code_length; k++ { + skip(parser) + } + } + } else { + // It is a non-escaped non-blank character. + s = read(parser, s) + } + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + } + + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // Check if we are at the end of the scalar. + if single { + if parser.buffer[parser.buffer_pos] == '\'' { + break + } + } else { + if parser.buffer[parser.buffer_pos] == '"' { + break + } + } + + // Consume blank characters. + for is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos) { + if is_blank(parser.buffer, parser.buffer_pos) { + // Consume a space or a tab character. + if !leading_blanks { + whitespaces = read(parser, whitespaces) + } else { + skip(parser) + } + } else { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + // Check if it is a first line break. + if !leading_blanks { + whitespaces = whitespaces[:0] + leading_break = read_line(parser, leading_break) + leading_blanks = true + } else { + trailing_breaks = read_line(parser, trailing_breaks) + } + } + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Join the whitespaces or fold line breaks. + if leading_blanks { + // Do we need to fold line breaks? + if len(leading_break) > 0 && leading_break[0] == '\n' { + if len(trailing_breaks) == 0 { + s = append(s, ' ') + } else { + s = append(s, trailing_breaks...) + } + } else { + s = append(s, leading_break...) + s = append(s, trailing_breaks...) + } + trailing_breaks = trailing_breaks[:0] + leading_break = leading_break[:0] + } else { + s = append(s, whitespaces...) + whitespaces = whitespaces[:0] + } + } + + // Eat the right quote. + skip(parser) + end_mark := parser.mark + + // Create a token. + *token = yaml_token_t{ + typ: yaml_SCALAR_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: s, + style: yaml_SINGLE_QUOTED_SCALAR_STYLE, + } + if !single { + token.style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + return true +} + +// Scan a plain scalar. +func yaml_parser_scan_plain_scalar(parser *yaml_parser_t, token *yaml_token_t) bool { + + var s, leading_break, trailing_breaks, whitespaces []byte + var leading_blanks bool + var indent = parser.indent + 1 + + start_mark := parser.mark + end_mark := parser.mark + + // Consume the content of the plain scalar. + for { + // Check for a document indicator. + if parser.unread < 4 && !yaml_parser_update_buffer(parser, 4) { + return false + } + if parser.mark.column == 0 && + ((parser.buffer[parser.buffer_pos+0] == '-' && + parser.buffer[parser.buffer_pos+1] == '-' && + parser.buffer[parser.buffer_pos+2] == '-') || + (parser.buffer[parser.buffer_pos+0] == '.' && + parser.buffer[parser.buffer_pos+1] == '.' && + parser.buffer[parser.buffer_pos+2] == '.')) && + is_blankz(parser.buffer, parser.buffer_pos+3) { + break + } + + // Check for a comment. + if parser.buffer[parser.buffer_pos] == '#' { + break + } + + // Consume non-blank characters. + for !is_blankz(parser.buffer, parser.buffer_pos) { + + // Check for indicators that may end a plain scalar. + if (parser.buffer[parser.buffer_pos] == ':' && is_blankz(parser.buffer, parser.buffer_pos+1)) || + (parser.flow_level > 0 && + (parser.buffer[parser.buffer_pos] == ',' || + parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == '[' || + parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '{' || + parser.buffer[parser.buffer_pos] == '}')) { + break + } + + // Check if we need to join whitespaces and breaks. + if leading_blanks || len(whitespaces) > 0 { + if leading_blanks { + // Do we need to fold line breaks? + if leading_break[0] == '\n' { + if len(trailing_breaks) == 0 { + s = append(s, ' ') + } else { + s = append(s, trailing_breaks...) + } + } else { + s = append(s, leading_break...) + s = append(s, trailing_breaks...) + } + trailing_breaks = trailing_breaks[:0] + leading_break = leading_break[:0] + leading_blanks = false + } else { + s = append(s, whitespaces...) + whitespaces = whitespaces[:0] + } + } + + // Copy the character. + s = read(parser, s) + + end_mark = parser.mark + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + } + + // Is it the end? + if !(is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos)) { + break + } + + // Consume blank characters. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos) { + if is_blank(parser.buffer, parser.buffer_pos) { + + // Check for tab characters that abuse indentation. + if leading_blanks && parser.mark.column < indent && is_tab(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a plain scalar", + start_mark, "found a tab character that violates indentation") + return false + } + + // Consume a space or a tab character. + if !leading_blanks { + whitespaces = read(parser, whitespaces) + } else { + skip(parser) + } + } else { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + // Check if it is a first line break. + if !leading_blanks { + whitespaces = whitespaces[:0] + leading_break = read_line(parser, leading_break) + leading_blanks = true + } else { + trailing_breaks = read_line(parser, trailing_breaks) + } + } + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check indentation level. + if parser.flow_level == 0 && parser.mark.column < indent { + break + } + } + + // Create a token. + *token = yaml_token_t{ + typ: yaml_SCALAR_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: s, + style: yaml_PLAIN_SCALAR_STYLE, + } + + // Note that we change the 'simple_key_allowed' flag. + if leading_blanks { + parser.simple_key_allowed = true + } + return true +} + +func yaml_parser_scan_line_comment(parser *yaml_parser_t, token_mark yaml_mark_t) bool { + if parser.newlines > 0 { + return true + } + + var start_mark yaml_mark_t + var text []byte + + for peek := 0; peek < 512; peek++ { + if parser.unread < peek+1 && !yaml_parser_update_buffer(parser, peek+1) { + break + } + if is_blank(parser.buffer, parser.buffer_pos+peek) { + continue + } + if parser.buffer[parser.buffer_pos+peek] == '#' { + seen := parser.mark.index + peek + for { + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if is_breakz(parser.buffer, parser.buffer_pos) { + if parser.mark.index >= seen { + break + } + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + } else if parser.mark.index >= seen { + if len(text) == 0 { + start_mark = parser.mark + } + text = read(parser, text) + } else { + skip(parser) + } + } + } + break + } + if len(text) > 0 { + parser.comments = append(parser.comments, yaml_comment_t{ + token_mark: token_mark, + start_mark: start_mark, + line: text, + }) + } + return true +} + +func yaml_parser_scan_comments(parser *yaml_parser_t, scan_mark yaml_mark_t) bool { + token := parser.tokens[len(parser.tokens)-1] + + if token.typ == yaml_FLOW_ENTRY_TOKEN && len(parser.tokens) > 1 { + token = parser.tokens[len(parser.tokens)-2] + } + + var token_mark = token.start_mark + var start_mark yaml_mark_t + var next_indent = parser.indent + if next_indent < 0 { + next_indent = 0 + } + + var recent_empty = false + var first_empty = parser.newlines <= 1 + + var line = parser.mark.line + var column = parser.mark.column + + var text []byte + + // The foot line is the place where a comment must start to + // still be considered as a foot of the prior content. + // If there's some content in the currently parsed line, then + // the foot is the line below it. + var foot_line = -1 + if scan_mark.line > 0 { + foot_line = parser.mark.line - parser.newlines + 1 + if parser.newlines == 0 && parser.mark.column > 1 { + foot_line++ + } + } + + var peek = 0 + for ; peek < 512; peek++ { + if parser.unread < peek+1 && !yaml_parser_update_buffer(parser, peek+1) { + break + } + column++ + if is_blank(parser.buffer, parser.buffer_pos+peek) { + continue + } + c := parser.buffer[parser.buffer_pos+peek] + var close_flow = parser.flow_level > 0 && (c == ']' || c == '}') + if close_flow || is_breakz(parser.buffer, parser.buffer_pos+peek) { + // Got line break or terminator. + if close_flow || !recent_empty { + if close_flow || first_empty && (start_mark.line == foot_line && token.typ != yaml_VALUE_TOKEN || start_mark.column-1 < next_indent) { + // This is the first empty line and there were no empty lines before, + // so this initial part of the comment is a foot of the prior token + // instead of being a head for the following one. Split it up. + // Alternatively, this might also be the last comment inside a flow + // scope, so it must be a footer. + if len(text) > 0 { + if start_mark.column-1 < next_indent { + // If dedented it's unrelated to the prior token. + token_mark = start_mark + } + parser.comments = append(parser.comments, yaml_comment_t{ + scan_mark: scan_mark, + token_mark: token_mark, + start_mark: start_mark, + end_mark: yaml_mark_t{parser.mark.index + peek, line, column}, + foot: text, + }) + scan_mark = yaml_mark_t{parser.mark.index + peek, line, column} + token_mark = scan_mark + text = nil + } + } else { + if len(text) > 0 && parser.buffer[parser.buffer_pos+peek] != 0 { + text = append(text, '\n') + } + } + } + if !is_break(parser.buffer, parser.buffer_pos+peek) { + break + } + first_empty = false + recent_empty = true + column = 0 + line++ + continue + } + + if len(text) > 0 && (close_flow || column-1 < next_indent && column != start_mark.column) { + // The comment at the different indentation is a foot of the + // preceding data rather than a head of the upcoming one. + parser.comments = append(parser.comments, yaml_comment_t{ + scan_mark: scan_mark, + token_mark: token_mark, + start_mark: start_mark, + end_mark: yaml_mark_t{parser.mark.index + peek, line, column}, + foot: text, + }) + scan_mark = yaml_mark_t{parser.mark.index + peek, line, column} + token_mark = scan_mark + text = nil + } + + if parser.buffer[parser.buffer_pos+peek] != '#' { + break + } + + if len(text) == 0 { + start_mark = yaml_mark_t{parser.mark.index + peek, line, column} + } else { + text = append(text, '\n') + } + + recent_empty = false + + // Consume until after the consumed comment line. + seen := parser.mark.index + peek + for { + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if is_breakz(parser.buffer, parser.buffer_pos) { + if parser.mark.index >= seen { + break + } + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + } else if parser.mark.index >= seen { + text = read(parser, text) + } else { + skip(parser) + } + } + + peek = 0 + column = 0 + line = parser.mark.line + next_indent = parser.indent + if next_indent < 0 { + next_indent = 0 + } + } + + if len(text) > 0 { + parser.comments = append(parser.comments, yaml_comment_t{ + scan_mark: scan_mark, + token_mark: start_mark, + start_mark: start_mark, + end_mark: yaml_mark_t{parser.mark.index + peek - 1, line, column}, + head: text, + }) + } + return true +} diff --git a/vendor/go.yaml.in/yaml/v3/sorter.go b/vendor/go.yaml.in/yaml/v3/sorter.go new file mode 100644 index 0000000000000..9210ece7e9723 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v3/sorter.go @@ -0,0 +1,134 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// +// 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 yaml + +import ( + "reflect" + "unicode" +) + +type keyList []reflect.Value + +func (l keyList) Len() int { return len(l) } +func (l keyList) Swap(i, j int) { l[i], l[j] = l[j], l[i] } +func (l keyList) Less(i, j int) bool { + a := l[i] + b := l[j] + ak := a.Kind() + bk := b.Kind() + for (ak == reflect.Interface || ak == reflect.Ptr) && !a.IsNil() { + a = a.Elem() + ak = a.Kind() + } + for (bk == reflect.Interface || bk == reflect.Ptr) && !b.IsNil() { + b = b.Elem() + bk = b.Kind() + } + af, aok := keyFloat(a) + bf, bok := keyFloat(b) + if aok && bok { + if af != bf { + return af < bf + } + if ak != bk { + return ak < bk + } + return numLess(a, b) + } + if ak != reflect.String || bk != reflect.String { + return ak < bk + } + ar, br := []rune(a.String()), []rune(b.String()) + digits := false + for i := 0; i < len(ar) && i < len(br); i++ { + if ar[i] == br[i] { + digits = unicode.IsDigit(ar[i]) + continue + } + al := unicode.IsLetter(ar[i]) + bl := unicode.IsLetter(br[i]) + if al && bl { + return ar[i] < br[i] + } + if al || bl { + if digits { + return al + } else { + return bl + } + } + var ai, bi int + var an, bn int64 + if ar[i] == '0' || br[i] == '0' { + for j := i - 1; j >= 0 && unicode.IsDigit(ar[j]); j-- { + if ar[j] != '0' { + an = 1 + bn = 1 + break + } + } + } + for ai = i; ai < len(ar) && unicode.IsDigit(ar[ai]); ai++ { + an = an*10 + int64(ar[ai]-'0') + } + for bi = i; bi < len(br) && unicode.IsDigit(br[bi]); bi++ { + bn = bn*10 + int64(br[bi]-'0') + } + if an != bn { + return an < bn + } + if ai != bi { + return ai < bi + } + return ar[i] < br[i] + } + return len(ar) < len(br) +} + +// keyFloat returns a float value for v if it is a number/bool +// and whether it is a number/bool or not. +func keyFloat(v reflect.Value) (f float64, ok bool) { + switch v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return float64(v.Int()), true + case reflect.Float32, reflect.Float64: + return v.Float(), true + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return float64(v.Uint()), true + case reflect.Bool: + if v.Bool() { + return 1, true + } + return 0, true + } + return 0, false +} + +// numLess returns whether a < b. +// a and b must necessarily have the same kind. +func numLess(a, b reflect.Value) bool { + switch a.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return a.Int() < b.Int() + case reflect.Float32, reflect.Float64: + return a.Float() < b.Float() + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return a.Uint() < b.Uint() + case reflect.Bool: + return !a.Bool() && b.Bool() + } + panic("not a number") +} diff --git a/vendor/go.yaml.in/yaml/v3/writerc.go b/vendor/go.yaml.in/yaml/v3/writerc.go new file mode 100644 index 0000000000000..266d0b092c031 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v3/writerc.go @@ -0,0 +1,48 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// Copyright (c) 2006-2010 Kirill Simonov +// +// 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. + +package yaml + +// Set the writer error and return false. +func yaml_emitter_set_writer_error(emitter *yaml_emitter_t, problem string) bool { + emitter.error = yaml_WRITER_ERROR + emitter.problem = problem + return false +} + +// Flush the output buffer. +func yaml_emitter_flush(emitter *yaml_emitter_t) bool { + if emitter.write_handler == nil { + panic("write handler not set") + } + + // Check if the buffer is empty. + if emitter.buffer_pos == 0 { + return true + } + + if err := emitter.write_handler(emitter, emitter.buffer[:emitter.buffer_pos]); err != nil { + return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error()) + } + emitter.buffer_pos = 0 + return true +} diff --git a/vendor/go.yaml.in/yaml/v3/yaml.go b/vendor/go.yaml.in/yaml/v3/yaml.go new file mode 100644 index 0000000000000..0b101cd20db90 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v3/yaml.go @@ -0,0 +1,703 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// +// 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 yaml implements YAML support for the Go language. +// +// Source code and other details for the project are available at GitHub: +// +// https://github.com/yaml/go-yaml +package yaml + +import ( + "errors" + "fmt" + "io" + "reflect" + "strings" + "sync" + "unicode/utf8" +) + +// The Unmarshaler interface may be implemented by types to customize their +// behavior when being unmarshaled from a YAML document. +type Unmarshaler interface { + UnmarshalYAML(value *Node) error +} + +type obsoleteUnmarshaler interface { + UnmarshalYAML(unmarshal func(interface{}) error) error +} + +// The Marshaler interface may be implemented by types to customize their +// behavior when being marshaled into a YAML document. The returned value +// is marshaled in place of the original value implementing Marshaler. +// +// If an error is returned by MarshalYAML, the marshaling procedure stops +// and returns with the provided error. +type Marshaler interface { + MarshalYAML() (interface{}, error) +} + +// Unmarshal decodes the first document found within the in byte slice +// and assigns decoded values into the out value. +// +// Maps and pointers (to a struct, string, int, etc) are accepted as out +// values. If an internal pointer within a struct is not initialized, +// the yaml package will initialize it if necessary for unmarshalling +// the provided data. The out parameter must not be nil. +// +// The type of the decoded values should be compatible with the respective +// values in out. If one or more values cannot be decoded due to a type +// mismatches, decoding continues partially until the end of the YAML +// content, and a *yaml.TypeError is returned with details for all +// missed values. +// +// Struct fields are only unmarshalled if they are exported (have an +// upper case first letter), and are unmarshalled using the field name +// lowercased as the default key. Custom keys may be defined via the +// "yaml" name in the field tag: the content preceding the first comma +// is used as the key, and the following comma-separated options are +// used to tweak the marshalling process (see Marshal). +// Conflicting names result in a runtime error. +// +// For example: +// +// type T struct { +// F int `yaml:"a,omitempty"` +// B int +// } +// var t T +// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t) +// +// See the documentation of Marshal for the format of tags and a list of +// supported tag options. +func Unmarshal(in []byte, out interface{}) (err error) { + return unmarshal(in, out, false) +} + +// A Decoder reads and decodes YAML values from an input stream. +type Decoder struct { + parser *parser + knownFields bool +} + +// NewDecoder returns a new decoder that reads from r. +// +// The decoder introduces its own buffering and may read +// data from r beyond the YAML values requested. +func NewDecoder(r io.Reader) *Decoder { + return &Decoder{ + parser: newParserFromReader(r), + } +} + +// KnownFields ensures that the keys in decoded mappings to +// exist as fields in the struct being decoded into. +func (dec *Decoder) KnownFields(enable bool) { + dec.knownFields = enable +} + +// Decode reads the next YAML-encoded value from its input +// and stores it in the value pointed to by v. +// +// See the documentation for Unmarshal for details about the +// conversion of YAML into a Go value. +func (dec *Decoder) Decode(v interface{}) (err error) { + d := newDecoder() + d.knownFields = dec.knownFields + defer handleErr(&err) + node := dec.parser.parse() + if node == nil { + return io.EOF + } + out := reflect.ValueOf(v) + if out.Kind() == reflect.Ptr && !out.IsNil() { + out = out.Elem() + } + d.unmarshal(node, out) + if len(d.terrors) > 0 { + return &TypeError{d.terrors} + } + return nil +} + +// Decode decodes the node and stores its data into the value pointed to by v. +// +// See the documentation for Unmarshal for details about the +// conversion of YAML into a Go value. +func (n *Node) Decode(v interface{}) (err error) { + d := newDecoder() + defer handleErr(&err) + out := reflect.ValueOf(v) + if out.Kind() == reflect.Ptr && !out.IsNil() { + out = out.Elem() + } + d.unmarshal(n, out) + if len(d.terrors) > 0 { + return &TypeError{d.terrors} + } + return nil +} + +func unmarshal(in []byte, out interface{}, strict bool) (err error) { + defer handleErr(&err) + d := newDecoder() + p := newParser(in) + defer p.destroy() + node := p.parse() + if node != nil { + v := reflect.ValueOf(out) + if v.Kind() == reflect.Ptr && !v.IsNil() { + v = v.Elem() + } + d.unmarshal(node, v) + } + if len(d.terrors) > 0 { + return &TypeError{d.terrors} + } + return nil +} + +// Marshal serializes the value provided into a YAML document. The structure +// of the generated document will reflect the structure of the value itself. +// Maps and pointers (to struct, string, int, etc) are accepted as the in value. +// +// Struct fields are only marshalled if they are exported (have an upper case +// first letter), and are marshalled using the field name lowercased as the +// default key. Custom keys may be defined via the "yaml" name in the field +// tag: the content preceding the first comma is used as the key, and the +// following comma-separated options are used to tweak the marshalling process. +// Conflicting names result in a runtime error. +// +// The field tag format accepted is: +// +// `(...) yaml:"[][,[,]]" (...)` +// +// The following flags are currently supported: +// +// omitempty Only include the field if it's not set to the zero +// value for the type or to empty slices or maps. +// Zero valued structs will be omitted if all their public +// fields are zero, unless they implement an IsZero +// method (see the IsZeroer interface type), in which +// case the field will be excluded if IsZero returns true. +// +// flow Marshal using a flow style (useful for structs, +// sequences and maps). +// +// inline Inline the field, which must be a struct or a map, +// causing all of its fields or keys to be processed as if +// they were part of the outer struct. For maps, keys must +// not conflict with the yaml keys of other struct fields. +// +// In addition, if the key is "-", the field is ignored. +// +// For example: +// +// type T struct { +// F int `yaml:"a,omitempty"` +// B int +// } +// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n" +// yaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n" +func Marshal(in interface{}) (out []byte, err error) { + defer handleErr(&err) + e := newEncoder() + defer e.destroy() + e.marshalDoc("", reflect.ValueOf(in)) + e.finish() + out = e.out + return +} + +// An Encoder writes YAML values to an output stream. +type Encoder struct { + encoder *encoder +} + +// NewEncoder returns a new encoder that writes to w. +// The Encoder should be closed after use to flush all data +// to w. +func NewEncoder(w io.Writer) *Encoder { + return &Encoder{ + encoder: newEncoderWithWriter(w), + } +} + +// Encode writes the YAML encoding of v to the stream. +// If multiple items are encoded to the stream, the +// second and subsequent document will be preceded +// with a "---" document separator, but the first will not. +// +// See the documentation for Marshal for details about the conversion of Go +// values to YAML. +func (e *Encoder) Encode(v interface{}) (err error) { + defer handleErr(&err) + e.encoder.marshalDoc("", reflect.ValueOf(v)) + return nil +} + +// Encode encodes value v and stores its representation in n. +// +// See the documentation for Marshal for details about the +// conversion of Go values into YAML. +func (n *Node) Encode(v interface{}) (err error) { + defer handleErr(&err) + e := newEncoder() + defer e.destroy() + e.marshalDoc("", reflect.ValueOf(v)) + e.finish() + p := newParser(e.out) + p.textless = true + defer p.destroy() + doc := p.parse() + *n = *doc.Content[0] + return nil +} + +// SetIndent changes the used indentation used when encoding. +func (e *Encoder) SetIndent(spaces int) { + if spaces < 0 { + panic("yaml: cannot indent to a negative number of spaces") + } + e.encoder.indent = spaces +} + +// CompactSeqIndent makes it so that '- ' is considered part of the indentation. +func (e *Encoder) CompactSeqIndent() { + e.encoder.emitter.compact_sequence_indent = true +} + +// DefaultSeqIndent makes it so that '- ' is not considered part of the indentation. +func (e *Encoder) DefaultSeqIndent() { + e.encoder.emitter.compact_sequence_indent = false +} + +// Close closes the encoder by writing any remaining data. +// It does not write a stream terminating string "...". +func (e *Encoder) Close() (err error) { + defer handleErr(&err) + e.encoder.finish() + return nil +} + +func handleErr(err *error) { + if v := recover(); v != nil { + if e, ok := v.(yamlError); ok { + *err = e.err + } else { + panic(v) + } + } +} + +type yamlError struct { + err error +} + +func fail(err error) { + panic(yamlError{err}) +} + +func failf(format string, args ...interface{}) { + panic(yamlError{fmt.Errorf("yaml: "+format, args...)}) +} + +// A TypeError is returned by Unmarshal when one or more fields in +// the YAML document cannot be properly decoded into the requested +// types. When this error is returned, the value is still +// unmarshaled partially. +type TypeError struct { + Errors []string +} + +func (e *TypeError) Error() string { + return fmt.Sprintf("yaml: unmarshal errors:\n %s", strings.Join(e.Errors, "\n ")) +} + +type Kind uint32 + +const ( + DocumentNode Kind = 1 << iota + SequenceNode + MappingNode + ScalarNode + AliasNode +) + +type Style uint32 + +const ( + TaggedStyle Style = 1 << iota + DoubleQuotedStyle + SingleQuotedStyle + LiteralStyle + FoldedStyle + FlowStyle +) + +// Node represents an element in the YAML document hierarchy. While documents +// are typically encoded and decoded into higher level types, such as structs +// and maps, Node is an intermediate representation that allows detailed +// control over the content being decoded or encoded. +// +// It's worth noting that although Node offers access into details such as +// line numbers, colums, and comments, the content when re-encoded will not +// have its original textual representation preserved. An effort is made to +// render the data plesantly, and to preserve comments near the data they +// describe, though. +// +// Values that make use of the Node type interact with the yaml package in the +// same way any other type would do, by encoding and decoding yaml data +// directly or indirectly into them. +// +// For example: +// +// var person struct { +// Name string +// Address yaml.Node +// } +// err := yaml.Unmarshal(data, &person) +// +// Or by itself: +// +// var person Node +// err := yaml.Unmarshal(data, &person) +type Node struct { + // Kind defines whether the node is a document, a mapping, a sequence, + // a scalar value, or an alias to another node. The specific data type of + // scalar nodes may be obtained via the ShortTag and LongTag methods. + Kind Kind + + // Style allows customizing the apperance of the node in the tree. + Style Style + + // Tag holds the YAML tag defining the data type for the value. + // When decoding, this field will always be set to the resolved tag, + // even when it wasn't explicitly provided in the YAML content. + // When encoding, if this field is unset the value type will be + // implied from the node properties, and if it is set, it will only + // be serialized into the representation if TaggedStyle is used or + // the implicit tag diverges from the provided one. + Tag string + + // Value holds the unescaped and unquoted represenation of the value. + Value string + + // Anchor holds the anchor name for this node, which allows aliases to point to it. + Anchor string + + // Alias holds the node that this alias points to. Only valid when Kind is AliasNode. + Alias *Node + + // Content holds contained nodes for documents, mappings, and sequences. + Content []*Node + + // HeadComment holds any comments in the lines preceding the node and + // not separated by an empty line. + HeadComment string + + // LineComment holds any comments at the end of the line where the node is in. + LineComment string + + // FootComment holds any comments following the node and before empty lines. + FootComment string + + // Line and Column hold the node position in the decoded YAML text. + // These fields are not respected when encoding the node. + Line int + Column int +} + +// IsZero returns whether the node has all of its fields unset. +func (n *Node) IsZero() bool { + return n.Kind == 0 && n.Style == 0 && n.Tag == "" && n.Value == "" && n.Anchor == "" && n.Alias == nil && n.Content == nil && + n.HeadComment == "" && n.LineComment == "" && n.FootComment == "" && n.Line == 0 && n.Column == 0 +} + +// LongTag returns the long form of the tag that indicates the data type for +// the node. If the Tag field isn't explicitly defined, one will be computed +// based on the node properties. +func (n *Node) LongTag() string { + return longTag(n.ShortTag()) +} + +// ShortTag returns the short form of the YAML tag that indicates data type for +// the node. If the Tag field isn't explicitly defined, one will be computed +// based on the node properties. +func (n *Node) ShortTag() string { + if n.indicatedString() { + return strTag + } + if n.Tag == "" || n.Tag == "!" { + switch n.Kind { + case MappingNode: + return mapTag + case SequenceNode: + return seqTag + case AliasNode: + if n.Alias != nil { + return n.Alias.ShortTag() + } + case ScalarNode: + tag, _ := resolve("", n.Value) + return tag + case 0: + // Special case to make the zero value convenient. + if n.IsZero() { + return nullTag + } + } + return "" + } + return shortTag(n.Tag) +} + +func (n *Node) indicatedString() bool { + return n.Kind == ScalarNode && + (shortTag(n.Tag) == strTag || + (n.Tag == "" || n.Tag == "!") && n.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0) +} + +// SetString is a convenience function that sets the node to a string value +// and defines its style in a pleasant way depending on its content. +func (n *Node) SetString(s string) { + n.Kind = ScalarNode + if utf8.ValidString(s) { + n.Value = s + n.Tag = strTag + } else { + n.Value = encodeBase64(s) + n.Tag = binaryTag + } + if strings.Contains(n.Value, "\n") { + n.Style = LiteralStyle + } +} + +// -------------------------------------------------------------------------- +// Maintain a mapping of keys to structure field indexes + +// The code in this section was copied from mgo/bson. + +// structInfo holds details for the serialization of fields of +// a given struct. +type structInfo struct { + FieldsMap map[string]fieldInfo + FieldsList []fieldInfo + + // InlineMap is the number of the field in the struct that + // contains an ,inline map, or -1 if there's none. + InlineMap int + + // InlineUnmarshalers holds indexes to inlined fields that + // contain unmarshaler values. + InlineUnmarshalers [][]int +} + +type fieldInfo struct { + Key string + Num int + OmitEmpty bool + Flow bool + // Id holds the unique field identifier, so we can cheaply + // check for field duplicates without maintaining an extra map. + Id int + + // Inline holds the field index if the field is part of an inlined struct. + Inline []int +} + +var structMap = make(map[reflect.Type]*structInfo) +var fieldMapMutex sync.RWMutex +var unmarshalerType reflect.Type + +func init() { + var v Unmarshaler + unmarshalerType = reflect.ValueOf(&v).Elem().Type() +} + +func getStructInfo(st reflect.Type) (*structInfo, error) { + fieldMapMutex.RLock() + sinfo, found := structMap[st] + fieldMapMutex.RUnlock() + if found { + return sinfo, nil + } + + n := st.NumField() + fieldsMap := make(map[string]fieldInfo) + fieldsList := make([]fieldInfo, 0, n) + inlineMap := -1 + inlineUnmarshalers := [][]int(nil) + for i := 0; i != n; i++ { + field := st.Field(i) + if field.PkgPath != "" && !field.Anonymous { + continue // Private field + } + + info := fieldInfo{Num: i} + + tag := field.Tag.Get("yaml") + if tag == "" && strings.Index(string(field.Tag), ":") < 0 { + tag = string(field.Tag) + } + if tag == "-" { + continue + } + + inline := false + fields := strings.Split(tag, ",") + if len(fields) > 1 { + for _, flag := range fields[1:] { + switch flag { + case "omitempty": + info.OmitEmpty = true + case "flow": + info.Flow = true + case "inline": + inline = true + default: + return nil, errors.New(fmt.Sprintf("unsupported flag %q in tag %q of type %s", flag, tag, st)) + } + } + tag = fields[0] + } + + if inline { + switch field.Type.Kind() { + case reflect.Map: + if inlineMap >= 0 { + return nil, errors.New("multiple ,inline maps in struct " + st.String()) + } + if field.Type.Key() != reflect.TypeOf("") { + return nil, errors.New("option ,inline needs a map with string keys in struct " + st.String()) + } + inlineMap = info.Num + case reflect.Struct, reflect.Ptr: + ftype := field.Type + for ftype.Kind() == reflect.Ptr { + ftype = ftype.Elem() + } + if ftype.Kind() != reflect.Struct { + return nil, errors.New("option ,inline may only be used on a struct or map field") + } + if reflect.PtrTo(ftype).Implements(unmarshalerType) { + inlineUnmarshalers = append(inlineUnmarshalers, []int{i}) + } else { + sinfo, err := getStructInfo(ftype) + if err != nil { + return nil, err + } + for _, index := range sinfo.InlineUnmarshalers { + inlineUnmarshalers = append(inlineUnmarshalers, append([]int{i}, index...)) + } + for _, finfo := range sinfo.FieldsList { + if _, found := fieldsMap[finfo.Key]; found { + msg := "duplicated key '" + finfo.Key + "' in struct " + st.String() + return nil, errors.New(msg) + } + if finfo.Inline == nil { + finfo.Inline = []int{i, finfo.Num} + } else { + finfo.Inline = append([]int{i}, finfo.Inline...) + } + finfo.Id = len(fieldsList) + fieldsMap[finfo.Key] = finfo + fieldsList = append(fieldsList, finfo) + } + } + default: + return nil, errors.New("option ,inline may only be used on a struct or map field") + } + continue + } + + if tag != "" { + info.Key = tag + } else { + info.Key = strings.ToLower(field.Name) + } + + if _, found = fieldsMap[info.Key]; found { + msg := "duplicated key '" + info.Key + "' in struct " + st.String() + return nil, errors.New(msg) + } + + info.Id = len(fieldsList) + fieldsList = append(fieldsList, info) + fieldsMap[info.Key] = info + } + + sinfo = &structInfo{ + FieldsMap: fieldsMap, + FieldsList: fieldsList, + InlineMap: inlineMap, + InlineUnmarshalers: inlineUnmarshalers, + } + + fieldMapMutex.Lock() + structMap[st] = sinfo + fieldMapMutex.Unlock() + return sinfo, nil +} + +// IsZeroer is used to check whether an object is zero to +// determine whether it should be omitted when marshaling +// with the omitempty flag. One notable implementation +// is time.Time. +type IsZeroer interface { + IsZero() bool +} + +func isZero(v reflect.Value) bool { + kind := v.Kind() + if z, ok := v.Interface().(IsZeroer); ok { + if (kind == reflect.Ptr || kind == reflect.Interface) && v.IsNil() { + return true + } + return z.IsZero() + } + switch kind { + case reflect.String: + return len(v.String()) == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Slice: + return v.Len() == 0 + case reflect.Map: + return v.Len() == 0 + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Struct: + vt := v.Type() + for i := v.NumField() - 1; i >= 0; i-- { + if vt.Field(i).PkgPath != "" { + continue // Private field + } + if !isZero(v.Field(i)) { + return false + } + } + return true + } + return false +} diff --git a/vendor/go.yaml.in/yaml/v3/yamlh.go b/vendor/go.yaml.in/yaml/v3/yamlh.go new file mode 100644 index 0000000000000..f59aa40f6401a --- /dev/null +++ b/vendor/go.yaml.in/yaml/v3/yamlh.go @@ -0,0 +1,811 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// Copyright (c) 2006-2010 Kirill Simonov +// +// 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. + +package yaml + +import ( + "fmt" + "io" +) + +// The version directive data. +type yaml_version_directive_t struct { + major int8 // The major version number. + minor int8 // The minor version number. +} + +// The tag directive data. +type yaml_tag_directive_t struct { + handle []byte // The tag handle. + prefix []byte // The tag prefix. +} + +type yaml_encoding_t int + +// The stream encoding. +const ( + // Let the parser choose the encoding. + yaml_ANY_ENCODING yaml_encoding_t = iota + + yaml_UTF8_ENCODING // The default UTF-8 encoding. + yaml_UTF16LE_ENCODING // The UTF-16-LE encoding with BOM. + yaml_UTF16BE_ENCODING // The UTF-16-BE encoding with BOM. +) + +type yaml_break_t int + +// Line break types. +const ( + // Let the parser choose the break type. + yaml_ANY_BREAK yaml_break_t = iota + + yaml_CR_BREAK // Use CR for line breaks (Mac style). + yaml_LN_BREAK // Use LN for line breaks (Unix style). + yaml_CRLN_BREAK // Use CR LN for line breaks (DOS style). +) + +type yaml_error_type_t int + +// Many bad things could happen with the parser and emitter. +const ( + // No error is produced. + yaml_NO_ERROR yaml_error_type_t = iota + + yaml_MEMORY_ERROR // Cannot allocate or reallocate a block of memory. + yaml_READER_ERROR // Cannot read or decode the input stream. + yaml_SCANNER_ERROR // Cannot scan the input stream. + yaml_PARSER_ERROR // Cannot parse the input stream. + yaml_COMPOSER_ERROR // Cannot compose a YAML document. + yaml_WRITER_ERROR // Cannot write to the output stream. + yaml_EMITTER_ERROR // Cannot emit a YAML stream. +) + +// The pointer position. +type yaml_mark_t struct { + index int // The position index. + line int // The position line. + column int // The position column. +} + +// Node Styles + +type yaml_style_t int8 + +type yaml_scalar_style_t yaml_style_t + +// Scalar styles. +const ( + // Let the emitter choose the style. + yaml_ANY_SCALAR_STYLE yaml_scalar_style_t = 0 + + yaml_PLAIN_SCALAR_STYLE yaml_scalar_style_t = 1 << iota // The plain scalar style. + yaml_SINGLE_QUOTED_SCALAR_STYLE // The single-quoted scalar style. + yaml_DOUBLE_QUOTED_SCALAR_STYLE // The double-quoted scalar style. + yaml_LITERAL_SCALAR_STYLE // The literal scalar style. + yaml_FOLDED_SCALAR_STYLE // The folded scalar style. +) + +type yaml_sequence_style_t yaml_style_t + +// Sequence styles. +const ( + // Let the emitter choose the style. + yaml_ANY_SEQUENCE_STYLE yaml_sequence_style_t = iota + + yaml_BLOCK_SEQUENCE_STYLE // The block sequence style. + yaml_FLOW_SEQUENCE_STYLE // The flow sequence style. +) + +type yaml_mapping_style_t yaml_style_t + +// Mapping styles. +const ( + // Let the emitter choose the style. + yaml_ANY_MAPPING_STYLE yaml_mapping_style_t = iota + + yaml_BLOCK_MAPPING_STYLE // The block mapping style. + yaml_FLOW_MAPPING_STYLE // The flow mapping style. +) + +// Tokens + +type yaml_token_type_t int + +// Token types. +const ( + // An empty token. + yaml_NO_TOKEN yaml_token_type_t = iota + + yaml_STREAM_START_TOKEN // A STREAM-START token. + yaml_STREAM_END_TOKEN // A STREAM-END token. + + yaml_VERSION_DIRECTIVE_TOKEN // A VERSION-DIRECTIVE token. + yaml_TAG_DIRECTIVE_TOKEN // A TAG-DIRECTIVE token. + yaml_DOCUMENT_START_TOKEN // A DOCUMENT-START token. + yaml_DOCUMENT_END_TOKEN // A DOCUMENT-END token. + + yaml_BLOCK_SEQUENCE_START_TOKEN // A BLOCK-SEQUENCE-START token. + yaml_BLOCK_MAPPING_START_TOKEN // A BLOCK-SEQUENCE-END token. + yaml_BLOCK_END_TOKEN // A BLOCK-END token. + + yaml_FLOW_SEQUENCE_START_TOKEN // A FLOW-SEQUENCE-START token. + yaml_FLOW_SEQUENCE_END_TOKEN // A FLOW-SEQUENCE-END token. + yaml_FLOW_MAPPING_START_TOKEN // A FLOW-MAPPING-START token. + yaml_FLOW_MAPPING_END_TOKEN // A FLOW-MAPPING-END token. + + yaml_BLOCK_ENTRY_TOKEN // A BLOCK-ENTRY token. + yaml_FLOW_ENTRY_TOKEN // A FLOW-ENTRY token. + yaml_KEY_TOKEN // A KEY token. + yaml_VALUE_TOKEN // A VALUE token. + + yaml_ALIAS_TOKEN // An ALIAS token. + yaml_ANCHOR_TOKEN // An ANCHOR token. + yaml_TAG_TOKEN // A TAG token. + yaml_SCALAR_TOKEN // A SCALAR token. +) + +func (tt yaml_token_type_t) String() string { + switch tt { + case yaml_NO_TOKEN: + return "yaml_NO_TOKEN" + case yaml_STREAM_START_TOKEN: + return "yaml_STREAM_START_TOKEN" + case yaml_STREAM_END_TOKEN: + return "yaml_STREAM_END_TOKEN" + case yaml_VERSION_DIRECTIVE_TOKEN: + return "yaml_VERSION_DIRECTIVE_TOKEN" + case yaml_TAG_DIRECTIVE_TOKEN: + return "yaml_TAG_DIRECTIVE_TOKEN" + case yaml_DOCUMENT_START_TOKEN: + return "yaml_DOCUMENT_START_TOKEN" + case yaml_DOCUMENT_END_TOKEN: + return "yaml_DOCUMENT_END_TOKEN" + case yaml_BLOCK_SEQUENCE_START_TOKEN: + return "yaml_BLOCK_SEQUENCE_START_TOKEN" + case yaml_BLOCK_MAPPING_START_TOKEN: + return "yaml_BLOCK_MAPPING_START_TOKEN" + case yaml_BLOCK_END_TOKEN: + return "yaml_BLOCK_END_TOKEN" + case yaml_FLOW_SEQUENCE_START_TOKEN: + return "yaml_FLOW_SEQUENCE_START_TOKEN" + case yaml_FLOW_SEQUENCE_END_TOKEN: + return "yaml_FLOW_SEQUENCE_END_TOKEN" + case yaml_FLOW_MAPPING_START_TOKEN: + return "yaml_FLOW_MAPPING_START_TOKEN" + case yaml_FLOW_MAPPING_END_TOKEN: + return "yaml_FLOW_MAPPING_END_TOKEN" + case yaml_BLOCK_ENTRY_TOKEN: + return "yaml_BLOCK_ENTRY_TOKEN" + case yaml_FLOW_ENTRY_TOKEN: + return "yaml_FLOW_ENTRY_TOKEN" + case yaml_KEY_TOKEN: + return "yaml_KEY_TOKEN" + case yaml_VALUE_TOKEN: + return "yaml_VALUE_TOKEN" + case yaml_ALIAS_TOKEN: + return "yaml_ALIAS_TOKEN" + case yaml_ANCHOR_TOKEN: + return "yaml_ANCHOR_TOKEN" + case yaml_TAG_TOKEN: + return "yaml_TAG_TOKEN" + case yaml_SCALAR_TOKEN: + return "yaml_SCALAR_TOKEN" + } + return "" +} + +// The token structure. +type yaml_token_t struct { + // The token type. + typ yaml_token_type_t + + // The start/end of the token. + start_mark, end_mark yaml_mark_t + + // The stream encoding (for yaml_STREAM_START_TOKEN). + encoding yaml_encoding_t + + // The alias/anchor/scalar value or tag/tag directive handle + // (for yaml_ALIAS_TOKEN, yaml_ANCHOR_TOKEN, yaml_SCALAR_TOKEN, yaml_TAG_TOKEN, yaml_TAG_DIRECTIVE_TOKEN). + value []byte + + // The tag suffix (for yaml_TAG_TOKEN). + suffix []byte + + // The tag directive prefix (for yaml_TAG_DIRECTIVE_TOKEN). + prefix []byte + + // The scalar style (for yaml_SCALAR_TOKEN). + style yaml_scalar_style_t + + // The version directive major/minor (for yaml_VERSION_DIRECTIVE_TOKEN). + major, minor int8 +} + +// Events + +type yaml_event_type_t int8 + +// Event types. +const ( + // An empty event. + yaml_NO_EVENT yaml_event_type_t = iota + + yaml_STREAM_START_EVENT // A STREAM-START event. + yaml_STREAM_END_EVENT // A STREAM-END event. + yaml_DOCUMENT_START_EVENT // A DOCUMENT-START event. + yaml_DOCUMENT_END_EVENT // A DOCUMENT-END event. + yaml_ALIAS_EVENT // An ALIAS event. + yaml_SCALAR_EVENT // A SCALAR event. + yaml_SEQUENCE_START_EVENT // A SEQUENCE-START event. + yaml_SEQUENCE_END_EVENT // A SEQUENCE-END event. + yaml_MAPPING_START_EVENT // A MAPPING-START event. + yaml_MAPPING_END_EVENT // A MAPPING-END event. + yaml_TAIL_COMMENT_EVENT +) + +var eventStrings = []string{ + yaml_NO_EVENT: "none", + yaml_STREAM_START_EVENT: "stream start", + yaml_STREAM_END_EVENT: "stream end", + yaml_DOCUMENT_START_EVENT: "document start", + yaml_DOCUMENT_END_EVENT: "document end", + yaml_ALIAS_EVENT: "alias", + yaml_SCALAR_EVENT: "scalar", + yaml_SEQUENCE_START_EVENT: "sequence start", + yaml_SEQUENCE_END_EVENT: "sequence end", + yaml_MAPPING_START_EVENT: "mapping start", + yaml_MAPPING_END_EVENT: "mapping end", + yaml_TAIL_COMMENT_EVENT: "tail comment", +} + +func (e yaml_event_type_t) String() string { + if e < 0 || int(e) >= len(eventStrings) { + return fmt.Sprintf("unknown event %d", e) + } + return eventStrings[e] +} + +// The event structure. +type yaml_event_t struct { + + // The event type. + typ yaml_event_type_t + + // The start and end of the event. + start_mark, end_mark yaml_mark_t + + // The document encoding (for yaml_STREAM_START_EVENT). + encoding yaml_encoding_t + + // The version directive (for yaml_DOCUMENT_START_EVENT). + version_directive *yaml_version_directive_t + + // The list of tag directives (for yaml_DOCUMENT_START_EVENT). + tag_directives []yaml_tag_directive_t + + // The comments + head_comment []byte + line_comment []byte + foot_comment []byte + tail_comment []byte + + // The anchor (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_ALIAS_EVENT). + anchor []byte + + // The tag (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT). + tag []byte + + // The scalar value (for yaml_SCALAR_EVENT). + value []byte + + // Is the document start/end indicator implicit, or the tag optional? + // (for yaml_DOCUMENT_START_EVENT, yaml_DOCUMENT_END_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_SCALAR_EVENT). + implicit bool + + // Is the tag optional for any non-plain style? (for yaml_SCALAR_EVENT). + quoted_implicit bool + + // The style (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT). + style yaml_style_t +} + +func (e *yaml_event_t) scalar_style() yaml_scalar_style_t { return yaml_scalar_style_t(e.style) } +func (e *yaml_event_t) sequence_style() yaml_sequence_style_t { return yaml_sequence_style_t(e.style) } +func (e *yaml_event_t) mapping_style() yaml_mapping_style_t { return yaml_mapping_style_t(e.style) } + +// Nodes + +const ( + yaml_NULL_TAG = "tag:yaml.org,2002:null" // The tag !!null with the only possible value: null. + yaml_BOOL_TAG = "tag:yaml.org,2002:bool" // The tag !!bool with the values: true and false. + yaml_STR_TAG = "tag:yaml.org,2002:str" // The tag !!str for string values. + yaml_INT_TAG = "tag:yaml.org,2002:int" // The tag !!int for integer values. + yaml_FLOAT_TAG = "tag:yaml.org,2002:float" // The tag !!float for float values. + yaml_TIMESTAMP_TAG = "tag:yaml.org,2002:timestamp" // The tag !!timestamp for date and time values. + + yaml_SEQ_TAG = "tag:yaml.org,2002:seq" // The tag !!seq is used to denote sequences. + yaml_MAP_TAG = "tag:yaml.org,2002:map" // The tag !!map is used to denote mapping. + + // Not in original libyaml. + yaml_BINARY_TAG = "tag:yaml.org,2002:binary" + yaml_MERGE_TAG = "tag:yaml.org,2002:merge" + + yaml_DEFAULT_SCALAR_TAG = yaml_STR_TAG // The default scalar tag is !!str. + yaml_DEFAULT_SEQUENCE_TAG = yaml_SEQ_TAG // The default sequence tag is !!seq. + yaml_DEFAULT_MAPPING_TAG = yaml_MAP_TAG // The default mapping tag is !!map. +) + +type yaml_node_type_t int + +// Node types. +const ( + // An empty node. + yaml_NO_NODE yaml_node_type_t = iota + + yaml_SCALAR_NODE // A scalar node. + yaml_SEQUENCE_NODE // A sequence node. + yaml_MAPPING_NODE // A mapping node. +) + +// An element of a sequence node. +type yaml_node_item_t int + +// An element of a mapping node. +type yaml_node_pair_t struct { + key int // The key of the element. + value int // The value of the element. +} + +// The node structure. +type yaml_node_t struct { + typ yaml_node_type_t // The node type. + tag []byte // The node tag. + + // The node data. + + // The scalar parameters (for yaml_SCALAR_NODE). + scalar struct { + value []byte // The scalar value. + length int // The length of the scalar value. + style yaml_scalar_style_t // The scalar style. + } + + // The sequence parameters (for YAML_SEQUENCE_NODE). + sequence struct { + items_data []yaml_node_item_t // The stack of sequence items. + style yaml_sequence_style_t // The sequence style. + } + + // The mapping parameters (for yaml_MAPPING_NODE). + mapping struct { + pairs_data []yaml_node_pair_t // The stack of mapping pairs (key, value). + pairs_start *yaml_node_pair_t // The beginning of the stack. + pairs_end *yaml_node_pair_t // The end of the stack. + pairs_top *yaml_node_pair_t // The top of the stack. + style yaml_mapping_style_t // The mapping style. + } + + start_mark yaml_mark_t // The beginning of the node. + end_mark yaml_mark_t // The end of the node. + +} + +// The document structure. +type yaml_document_t struct { + + // The document nodes. + nodes []yaml_node_t + + // The version directive. + version_directive *yaml_version_directive_t + + // The list of tag directives. + tag_directives_data []yaml_tag_directive_t + tag_directives_start int // The beginning of the tag directives list. + tag_directives_end int // The end of the tag directives list. + + start_implicit int // Is the document start indicator implicit? + end_implicit int // Is the document end indicator implicit? + + // The start/end of the document. + start_mark, end_mark yaml_mark_t +} + +// The prototype of a read handler. +// +// The read handler is called when the parser needs to read more bytes from the +// source. The handler should write not more than size bytes to the buffer. +// The number of written bytes should be set to the size_read variable. +// +// [in,out] data A pointer to an application data specified by +// +// yaml_parser_set_input(). +// +// [out] buffer The buffer to write the data from the source. +// [in] size The size of the buffer. +// [out] size_read The actual number of bytes read from the source. +// +// On success, the handler should return 1. If the handler failed, +// the returned value should be 0. On EOF, the handler should set the +// size_read to 0 and return 1. +type yaml_read_handler_t func(parser *yaml_parser_t, buffer []byte) (n int, err error) + +// This structure holds information about a potential simple key. +type yaml_simple_key_t struct { + possible bool // Is a simple key possible? + required bool // Is a simple key required? + token_number int // The number of the token. + mark yaml_mark_t // The position mark. +} + +// The states of the parser. +type yaml_parser_state_t int + +const ( + yaml_PARSE_STREAM_START_STATE yaml_parser_state_t = iota + + yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE // Expect the beginning of an implicit document. + yaml_PARSE_DOCUMENT_START_STATE // Expect DOCUMENT-START. + yaml_PARSE_DOCUMENT_CONTENT_STATE // Expect the content of a document. + yaml_PARSE_DOCUMENT_END_STATE // Expect DOCUMENT-END. + yaml_PARSE_BLOCK_NODE_STATE // Expect a block node. + yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE // Expect a block node or indentless sequence. + yaml_PARSE_FLOW_NODE_STATE // Expect a flow node. + yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a block sequence. + yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE // Expect an entry of a block sequence. + yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE // Expect an entry of an indentless sequence. + yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping. + yaml_PARSE_BLOCK_MAPPING_KEY_STATE // Expect a block mapping key. + yaml_PARSE_BLOCK_MAPPING_VALUE_STATE // Expect a block mapping value. + yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a flow sequence. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE // Expect an entry of a flow sequence. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE // Expect a key of an ordered mapping. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE // Expect a value of an ordered mapping. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE // Expect the and of an ordered mapping entry. + yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping. + yaml_PARSE_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping. + yaml_PARSE_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping. + yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE // Expect an empty value of a flow mapping. + yaml_PARSE_END_STATE // Expect nothing. +) + +func (ps yaml_parser_state_t) String() string { + switch ps { + case yaml_PARSE_STREAM_START_STATE: + return "yaml_PARSE_STREAM_START_STATE" + case yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE: + return "yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE" + case yaml_PARSE_DOCUMENT_START_STATE: + return "yaml_PARSE_DOCUMENT_START_STATE" + case yaml_PARSE_DOCUMENT_CONTENT_STATE: + return "yaml_PARSE_DOCUMENT_CONTENT_STATE" + case yaml_PARSE_DOCUMENT_END_STATE: + return "yaml_PARSE_DOCUMENT_END_STATE" + case yaml_PARSE_BLOCK_NODE_STATE: + return "yaml_PARSE_BLOCK_NODE_STATE" + case yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE: + return "yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE" + case yaml_PARSE_FLOW_NODE_STATE: + return "yaml_PARSE_FLOW_NODE_STATE" + case yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE: + return "yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE" + case yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE: + return "yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE" + case yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE: + return "yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE" + case yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE: + return "yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE" + case yaml_PARSE_BLOCK_MAPPING_KEY_STATE: + return "yaml_PARSE_BLOCK_MAPPING_KEY_STATE" + case yaml_PARSE_BLOCK_MAPPING_VALUE_STATE: + return "yaml_PARSE_BLOCK_MAPPING_VALUE_STATE" + case yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE" + case yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE: + return "yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE" + case yaml_PARSE_FLOW_MAPPING_KEY_STATE: + return "yaml_PARSE_FLOW_MAPPING_KEY_STATE" + case yaml_PARSE_FLOW_MAPPING_VALUE_STATE: + return "yaml_PARSE_FLOW_MAPPING_VALUE_STATE" + case yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE: + return "yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE" + case yaml_PARSE_END_STATE: + return "yaml_PARSE_END_STATE" + } + return "" +} + +// This structure holds aliases data. +type yaml_alias_data_t struct { + anchor []byte // The anchor. + index int // The node id. + mark yaml_mark_t // The anchor mark. +} + +// The parser structure. +// +// All members are internal. Manage the structure using the +// yaml_parser_ family of functions. +type yaml_parser_t struct { + + // Error handling + + error yaml_error_type_t // Error type. + + problem string // Error description. + + // The byte about which the problem occurred. + problem_offset int + problem_value int + problem_mark yaml_mark_t + + // The error context. + context string + context_mark yaml_mark_t + + // Reader stuff + + read_handler yaml_read_handler_t // Read handler. + + input_reader io.Reader // File input data. + input []byte // String input data. + input_pos int + + eof bool // EOF flag + + buffer []byte // The working buffer. + buffer_pos int // The current position of the buffer. + + unread int // The number of unread characters in the buffer. + + newlines int // The number of line breaks since last non-break/non-blank character + + raw_buffer []byte // The raw buffer. + raw_buffer_pos int // The current position of the buffer. + + encoding yaml_encoding_t // The input encoding. + + offset int // The offset of the current position (in bytes). + mark yaml_mark_t // The mark of the current position. + + // Comments + + head_comment []byte // The current head comments + line_comment []byte // The current line comments + foot_comment []byte // The current foot comments + tail_comment []byte // Foot comment that happens at the end of a block. + stem_comment []byte // Comment in item preceding a nested structure (list inside list item, etc) + + comments []yaml_comment_t // The folded comments for all parsed tokens + comments_head int + + // Scanner stuff + + stream_start_produced bool // Have we started to scan the input stream? + stream_end_produced bool // Have we reached the end of the input stream? + + flow_level int // The number of unclosed '[' and '{' indicators. + + tokens []yaml_token_t // The tokens queue. + tokens_head int // The head of the tokens queue. + tokens_parsed int // The number of tokens fetched from the queue. + token_available bool // Does the tokens queue contain a token ready for dequeueing. + + indent int // The current indentation level. + indents []int // The indentation levels stack. + + simple_key_allowed bool // May a simple key occur at the current position? + simple_keys []yaml_simple_key_t // The stack of simple keys. + simple_keys_by_tok map[int]int // possible simple_key indexes indexed by token_number + + // Parser stuff + + state yaml_parser_state_t // The current parser state. + states []yaml_parser_state_t // The parser states stack. + marks []yaml_mark_t // The stack of marks. + tag_directives []yaml_tag_directive_t // The list of TAG directives. + + // Dumper stuff + + aliases []yaml_alias_data_t // The alias data. + + document *yaml_document_t // The currently parsed document. +} + +type yaml_comment_t struct { + scan_mark yaml_mark_t // Position where scanning for comments started + token_mark yaml_mark_t // Position after which tokens will be associated with this comment + start_mark yaml_mark_t // Position of '#' comment mark + end_mark yaml_mark_t // Position where comment terminated + + head []byte + line []byte + foot []byte +} + +// Emitter Definitions + +// The prototype of a write handler. +// +// The write handler is called when the emitter needs to flush the accumulated +// characters to the output. The handler should write @a size bytes of the +// @a buffer to the output. +// +// @param[in,out] data A pointer to an application data specified by +// +// yaml_emitter_set_output(). +// +// @param[in] buffer The buffer with bytes to be written. +// @param[in] size The size of the buffer. +// +// @returns On success, the handler should return @c 1. If the handler failed, +// the returned value should be @c 0. +type yaml_write_handler_t func(emitter *yaml_emitter_t, buffer []byte) error + +type yaml_emitter_state_t int + +// The emitter states. +const ( + // Expect STREAM-START. + yaml_EMIT_STREAM_START_STATE yaml_emitter_state_t = iota + + yaml_EMIT_FIRST_DOCUMENT_START_STATE // Expect the first DOCUMENT-START or STREAM-END. + yaml_EMIT_DOCUMENT_START_STATE // Expect DOCUMENT-START or STREAM-END. + yaml_EMIT_DOCUMENT_CONTENT_STATE // Expect the content of a document. + yaml_EMIT_DOCUMENT_END_STATE // Expect DOCUMENT-END. + yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a flow sequence. + yaml_EMIT_FLOW_SEQUENCE_TRAIL_ITEM_STATE // Expect the next item of a flow sequence, with the comma already written out + yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE // Expect an item of a flow sequence. + yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping. + yaml_EMIT_FLOW_MAPPING_TRAIL_KEY_STATE // Expect the next key of a flow mapping, with the comma already written out + yaml_EMIT_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping. + yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a flow mapping. + yaml_EMIT_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping. + yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a block sequence. + yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE // Expect an item of a block sequence. + yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping. + yaml_EMIT_BLOCK_MAPPING_KEY_STATE // Expect the key of a block mapping. + yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a block mapping. + yaml_EMIT_BLOCK_MAPPING_VALUE_STATE // Expect a value of a block mapping. + yaml_EMIT_END_STATE // Expect nothing. +) + +// The emitter structure. +// +// All members are internal. Manage the structure using the @c yaml_emitter_ +// family of functions. +type yaml_emitter_t struct { + + // Error handling + + error yaml_error_type_t // Error type. + problem string // Error description. + + // Writer stuff + + write_handler yaml_write_handler_t // Write handler. + + output_buffer *[]byte // String output data. + output_writer io.Writer // File output data. + + buffer []byte // The working buffer. + buffer_pos int // The current position of the buffer. + + raw_buffer []byte // The raw buffer. + raw_buffer_pos int // The current position of the buffer. + + encoding yaml_encoding_t // The stream encoding. + + // Emitter stuff + + canonical bool // If the output is in the canonical style? + best_indent int // The number of indentation spaces. + best_width int // The preferred width of the output lines. + unicode bool // Allow unescaped non-ASCII characters? + line_break yaml_break_t // The preferred line break. + + state yaml_emitter_state_t // The current emitter state. + states []yaml_emitter_state_t // The stack of states. + + events []yaml_event_t // The event queue. + events_head int // The head of the event queue. + + indents []int // The stack of indentation levels. + + tag_directives []yaml_tag_directive_t // The list of tag directives. + + indent int // The current indentation level. + + compact_sequence_indent bool // Is '- ' is considered part of the indentation for sequence elements? + + flow_level int // The current flow level. + + root_context bool // Is it the document root context? + sequence_context bool // Is it a sequence context? + mapping_context bool // Is it a mapping context? + simple_key_context bool // Is it a simple mapping key context? + + line int // The current line. + column int // The current column. + whitespace bool // If the last character was a whitespace? + indention bool // If the last character was an indentation character (' ', '-', '?', ':')? + open_ended bool // If an explicit document end is required? + + space_above bool // Is there's an empty line above? + foot_indent int // The indent used to write the foot comment above, or -1 if none. + + // Anchor analysis. + anchor_data struct { + anchor []byte // The anchor value. + alias bool // Is it an alias? + } + + // Tag analysis. + tag_data struct { + handle []byte // The tag handle. + suffix []byte // The tag suffix. + } + + // Scalar analysis. + scalar_data struct { + value []byte // The scalar value. + multiline bool // Does the scalar contain line breaks? + flow_plain_allowed bool // Can the scalar be expessed in the flow plain style? + block_plain_allowed bool // Can the scalar be expressed in the block plain style? + single_quoted_allowed bool // Can the scalar be expressed in the single quoted style? + block_allowed bool // Can the scalar be expressed in the literal or folded styles? + style yaml_scalar_style_t // The output style. + } + + // Comments + head_comment []byte + line_comment []byte + foot_comment []byte + tail_comment []byte + + key_line_comment []byte + + // Dumper stuff + + opened bool // If the stream was already opened? + closed bool // If the stream was already closed? + + // The information associated with the document nodes. + anchors *struct { + references int // The number of references. + anchor int // The anchor id. + serialized bool // If the node has been emitted? + } + + last_anchor_id int // The last assigned anchor id. + + document *yaml_document_t // The currently emitted document. +} diff --git a/vendor/go.yaml.in/yaml/v3/yamlprivateh.go b/vendor/go.yaml.in/yaml/v3/yamlprivateh.go new file mode 100644 index 0000000000000..dea1ba9610dfd --- /dev/null +++ b/vendor/go.yaml.in/yaml/v3/yamlprivateh.go @@ -0,0 +1,198 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// Copyright (c) 2006-2010 Kirill Simonov +// +// 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. + +package yaml + +const ( + // The size of the input raw buffer. + input_raw_buffer_size = 512 + + // The size of the input buffer. + // It should be possible to decode the whole raw buffer. + input_buffer_size = input_raw_buffer_size * 3 + + // The size of the output buffer. + output_buffer_size = 128 + + // The size of the output raw buffer. + // It should be possible to encode the whole output buffer. + output_raw_buffer_size = (output_buffer_size*2 + 2) + + // The size of other stacks and queues. + initial_stack_size = 16 + initial_queue_size = 16 + initial_string_size = 16 +) + +// Check if the character at the specified position is an alphabetical +// character, a digit, '_', or '-'. +func is_alpha(b []byte, i int) bool { + return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'Z' || b[i] >= 'a' && b[i] <= 'z' || b[i] == '_' || b[i] == '-' +} + +// Check if the character at the specified position is a digit. +func is_digit(b []byte, i int) bool { + return b[i] >= '0' && b[i] <= '9' +} + +// Get the value of a digit. +func as_digit(b []byte, i int) int { + return int(b[i]) - '0' +} + +// Check if the character at the specified position is a hex-digit. +func is_hex(b []byte, i int) bool { + return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'F' || b[i] >= 'a' && b[i] <= 'f' +} + +// Get the value of a hex-digit. +func as_hex(b []byte, i int) int { + bi := b[i] + if bi >= 'A' && bi <= 'F' { + return int(bi) - 'A' + 10 + } + if bi >= 'a' && bi <= 'f' { + return int(bi) - 'a' + 10 + } + return int(bi) - '0' +} + +// Check if the character is ASCII. +func is_ascii(b []byte, i int) bool { + return b[i] <= 0x7F +} + +// Check if the character at the start of the buffer can be printed unescaped. +func is_printable(b []byte, i int) bool { + return ((b[i] == 0x0A) || // . == #x0A + (b[i] >= 0x20 && b[i] <= 0x7E) || // #x20 <= . <= #x7E + (b[i] == 0xC2 && b[i+1] >= 0xA0) || // #0xA0 <= . <= #xD7FF + (b[i] > 0xC2 && b[i] < 0xED) || + (b[i] == 0xED && b[i+1] < 0xA0) || + (b[i] == 0xEE) || + (b[i] == 0xEF && // #xE000 <= . <= #xFFFD + !(b[i+1] == 0xBB && b[i+2] == 0xBF) && // && . != #xFEFF + !(b[i+1] == 0xBF && (b[i+2] == 0xBE || b[i+2] == 0xBF)))) +} + +// Check if the character at the specified position is NUL. +func is_z(b []byte, i int) bool { + return b[i] == 0x00 +} + +// Check if the beginning of the buffer is a BOM. +func is_bom(b []byte, i int) bool { + return b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF +} + +// Check if the character at the specified position is space. +func is_space(b []byte, i int) bool { + return b[i] == ' ' +} + +// Check if the character at the specified position is tab. +func is_tab(b []byte, i int) bool { + return b[i] == '\t' +} + +// Check if the character at the specified position is blank (space or tab). +func is_blank(b []byte, i int) bool { + //return is_space(b, i) || is_tab(b, i) + return b[i] == ' ' || b[i] == '\t' +} + +// Check if the character at the specified position is a line break. +func is_break(b []byte, i int) bool { + return (b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9) // PS (#x2029) +} + +func is_crlf(b []byte, i int) bool { + return b[i] == '\r' && b[i+1] == '\n' +} + +// Check if the character is a line break or NUL. +func is_breakz(b []byte, i int) bool { + //return is_break(b, i) || is_z(b, i) + return ( + // is_break: + b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) + // is_z: + b[i] == 0) +} + +// Check if the character is a line break, space, or NUL. +func is_spacez(b []byte, i int) bool { + //return is_space(b, i) || is_breakz(b, i) + return ( + // is_space: + b[i] == ' ' || + // is_breakz: + b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) + b[i] == 0) +} + +// Check if the character is a line break, space, tab, or NUL. +func is_blankz(b []byte, i int) bool { + //return is_blank(b, i) || is_breakz(b, i) + return ( + // is_blank: + b[i] == ' ' || b[i] == '\t' || + // is_breakz: + b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) + b[i] == 0) +} + +// Determine the width of the character. +func width(b byte) int { + // Don't replace these by a switch without first + // confirming that it is being inlined. + if b&0x80 == 0x00 { + return 1 + } + if b&0xE0 == 0xC0 { + return 2 + } + if b&0xF0 == 0xE0 { + return 3 + } + if b&0xF8 == 0xF0 { + return 4 + } + return 0 + +} diff --git a/vendor/modules.txt b/vendor/modules.txt index b458269402811..40c1b91058722 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -35,7 +35,9 @@ github.com/Microsoft/hnslib/internal/runhcs ## explicit; go 1.11 github.com/NYTimes/gziphandler # github.com/antlr4-go/antlr/v4 v4.13.0 -## explicit; go 1.20 +## explicit; go 1.22 +# github.com/antlr4-go/antlr/v4 v4.13.1 +## explicit; go 1.22 github.com/antlr4-go/antlr/v4 # github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 ## explicit @@ -275,8 +277,11 @@ github.com/google/cadvisor/utils/sysinfo github.com/google/cadvisor/version github.com/google/cadvisor/watcher # github.com/google/cel-go v0.23.2 -## explicit; go 1.21.1 +## explicit; go 1.23.0 +# github.com/google/cel-go v0.30.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 @@ -284,6 +289,7 @@ github.com/google/cel-go/common/ast github.com/google/cel-go/common/containers github.com/google/cel-go/common/debug 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/operators github.com/google/cel-go/common/overloads @@ -557,7 +563,6 @@ github.com/spf13/cobra/doc github.com/spf13/pflag # 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 @@ -785,6 +790,9 @@ go.uber.org/zap/internal/ztest go.uber.org/zap/zapcore go.uber.org/zap/zapgrpc go.uber.org/zap/zaptest +# go.yaml.in/yaml/v3 v3.0.4 +## explicit; go 1.16 +go.yaml.in/yaml/v3 # golang.org/x/crypto v0.55.0 ## explicit; go 1.25.0 golang.org/x/crypto/bcrypt @@ -804,6 +812,8 @@ golang.org/x/crypto/ssh golang.org/x/crypto/ssh/internal/bcrypt_pbkdf # golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 ## explicit; go 1.20 +# golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 +## explicit; go 1.20 golang.org/x/exp/constraints golang.org/x/exp/slices # golang.org/x/mod v0.38.0