Merge branch 'origin/main' into 'next-release/main'

This commit is contained in:
oauth
2026-07-02 06:14:45 +00:00
122 changed files with 12014 additions and 17672 deletions

10
go.mod
View File

@@ -61,7 +61,7 @@ require (
github.com/onsi/ginkgo v1.16.5
github.com/onsi/ginkgo/v2 v2.32.0
github.com/onsi/gomega v1.42.1
github.com/open-policy-agent/opa v1.17.1
github.com/open-policy-agent/opa v1.18.1
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260310090739-853d972b282d
github.com/opencloud-eu/reva/v2 v2.46.4-0.20260625152426-8cff2a7032ec
@@ -72,7 +72,7 @@ require (
github.com/prometheus/client_golang v1.23.2
github.com/r3labs/sse/v2 v2.10.0
github.com/riandyrn/otelchi v0.12.3
github.com/rogpeppe/go-internal v1.14.1
github.com/rogpeppe/go-internal v1.15.0
github.com/rs/cors v1.11.1
github.com/rs/zerolog v1.35.1
github.com/sirupsen/logrus v1.9.4
@@ -95,7 +95,7 @@ require (
go-micro.dev/v4 v4.11.0
go.etcd.io/bbolt v1.5.0
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0
go.opentelemetry.io/contrib/zpages v0.69.0
go.opentelemetry.io/otel v1.44.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0
@@ -111,7 +111,7 @@ require (
golang.org/x/term v0.44.0
golang.org/x/text v0.38.0
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa
google.golang.org/grpc v1.81.1
google.golang.org/grpc v1.82.0
google.golang.org/protobuf v1.36.11
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
@@ -367,7 +367,7 @@ require (
github.com/trustelem/zxcvbn v1.0.1 // indirect
github.com/urfave/cli/v2 v2.27.7 // indirect
github.com/valyala/fastjson v1.6.10 // indirect
github.com/vektah/gqlparser/v2 v2.5.33 // indirect
github.com/vektah/gqlparser/v2 v2.5.34 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/wk8/go-ordered-map v1.0.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect

26
go.sum
View File

@@ -113,8 +113,6 @@ github.com/alexedwards/argon2id v1.0.0/go.mod h1:tYKkqIjzXvZdzPvADMWOEZ+l6+BD6Ct
github.com/aliyun/alibaba-cloud-sdk-go v1.61.976/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964 h1:I9YN9WMo3SUh7p/4wKeNvD/IQla3U3SUa61U7ul+xM4=
github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964/go.mod h1:eFiR01PwTcpbzXtdMces7zxg6utvFM5puiWHpWB8D/k=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
@@ -283,8 +281,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjY
github.com/deepmap/oapi-codegen v1.3.11/go.mod h1:suMvK7+rKlx3+tpa8ByptmvoXbAV70wERKTOGH3hLp0=
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I=
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE=
github.com/dgraph-io/badger/v4 v4.9.1 h1:DocZXZkg5JJHJPtUErA0ibyHxOVUDVoXLSCV6t8NC8w=
github.com/dgraph-io/badger/v4 v4.9.1/go.mod h1:5/MEx97uzdPUHR4KtkNt8asfI2T4JiEiQlV7kWUo8c0=
github.com/dgraph-io/badger/v4 v4.9.2 h1:Wb5qw8gElqwV1a8msHTeQKova9b1V10heFKMIiPd80E=
github.com/dgraph-io/badger/v4 v4.9.2/go.mod h1:nJjaJTUOSsQEBhsq209FmwCvMJzEA3e74RjZw6V2pQI=
github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE=
github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU=
github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM=
@@ -941,8 +939,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.42.1 h1:iN1rCUX+44NZ1Dc97MPoeFYbFR0vh8zxoxMFwKdyZ6I=
github.com/onsi/gomega v1.42.1/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg=
github.com/open-policy-agent/opa v1.17.1 h1:wO0MOux/VCqY41aVAD6Toe1p3A7O7DlRZ1RHmYSpoS8=
github.com/open-policy-agent/opa v1.17.1/go.mod h1:lcuZYSlqQpXFzsA6EJCELmfR5+nNOpZYX+eo7xaIIlk=
github.com/open-policy-agent/opa v1.18.1 h1:PQXN8BZN0zRf5NcI7yt1LfQ7pF1pLgWVAGNM3WgJI/4=
github.com/open-policy-agent/opa v1.18.1/go.mod h1:9GY+hER4ZEXtxPlMjftVbqJJY9xLtCD3Q0oufRCfAKo=
github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a h1:Sakl76blJAaM6NxylVkgSzktjo2dS504iDotEFJsh3M=
github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a/go.mod h1:pjcozWijkNPbEtX5SIQaxEW/h8VAVZYTLx+70bmB3LY=
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89 h1:W1ms+lP5lUUIzjRGDg93WrQfZJZCaV1ZP3KeyXi8bzY=
@@ -1070,8 +1068,8 @@ github.com/riandyrn/otelchi v0.12.3 h1:KW9gA+97d6mExk8vbh0FRwb2biUvpyYlc8YuxP1Oa
github.com/riandyrn/otelchi v0.12.3/go.mod h1:weZZeUJURvtCcbWsdb7Y6F8KFZGedJlSrgUjq9VirV8=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
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/rogpeppe/go-internal v1.15.0 h1:D0RCU5rMAp+SpgkiNdrjfJ+LX4J1M32V2NeCY7EJ6hc=
github.com/rogpeppe/go-internal v1.15.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
@@ -1236,8 +1234,8 @@ github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADT
github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/vektah/gqlparser/v2 v2.5.33 h1:lRp8aIeNUNbimf/axZd7ETg24q06hBtPaas+TcvI/7E=
github.com/vektah/gqlparser/v2 v2.5.33/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts=
github.com/vektah/gqlparser/v2 v2.5.34 h1:MEea5P0qhdcqfBL45ghKE+qr9laidVHTMHjav5h7ckk=
github.com/vektah/gqlparser/v2 v2.5.34/go.mod h1:mFdHLGCio7OGX1fby9ZjTW6FN+qxgmbnBcRIeeScE5s=
github.com/vinyldns/go-vinyldns v0.0.0-20200917153823-148a5f6b8f14/go.mod h1:RWc47jtnVuQv6+lY3c768WtXCas/Xi+U5UFc5xULmYg=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
@@ -1303,8 +1301,8 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0 h1:2yEATaop1/a1I4psnSLgWVPLWwCzkqWakgJy7xTDVy0=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0/go.mod h1:D7J12YRapIekYyPWgGPlA/23pRmpSEZC5xJC/TTLI9U=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI=
go.opentelemetry.io/contrib/zpages v0.69.0 h1:YQC1PumJq6lUGQNrLW14ID9a0dXSBcbC/aC6VRSIARU=
go.opentelemetry.io/contrib/zpages v0.69.0/go.mod h1:FGvUcMGN5atRzUIgUsqOi+MiMvlQsfbuYATBHPP4cGs=
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
@@ -1731,8 +1729,8 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
google.golang.org/grpc v1.82.0 h1:vguDnZUPjE26w09A63VoxZPnvPjB5Riyc0mkXPFmAIU=
google.golang.org/grpc v1.82.0/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
google.golang.org/grpc/examples v0.0.0-20211102180624-670c133e568e h1:m7aQHHqd0q89mRwhwS9Bx2rjyl/hsFAeta+uGrHsQaU=
google.golang.org/grpc/examples v0.0.0-20211102180624-670c133e568e/go.mod h1:gID3PKrg7pWKntu9Ss6zTLJ0ttC0X9IHgREOCZwbCVU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Ivan Fustero, 2025\n"
"Language-Team: Catalan (https://app.transifex.com/opencloud-eu/teams/204053/ca/)\n"

View File

@@ -13,7 +13,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:59+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Jannik Stehle, 2026\n"
"Language-Team: German (https://app.transifex.com/opencloud-eu/teams/204053/de/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Efstathios Iosifidis <eiosifidis@gmail.com>, 2026\n"
"Language-Team: Greek (https://app.transifex.com/opencloud-eu/teams/204053/el/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: eric_G <junk.eg@free.fr>, 2025\n"
"Language-Team: French (https://app.transifex.com/opencloud-eu/teams/204053/fr/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: mitibor, 2026\n"
"Language-Team: Hungarian (https://app.transifex.com/opencloud-eu/teams/204053/hu/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Simone Broglia, 2025\n"
"Language-Team: Italian (https://app.transifex.com/opencloud-eu/teams/204053/it/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: iikaka88, 2025\n"
"Language-Team: Japanese (https://app.transifex.com/opencloud-eu/teams/204053/ja/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: gapho shin, 2025\n"
"Language-Team: Korean (https://app.transifex.com/opencloud-eu/teams/204053/ko/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: BoneNI, 2026\n"
"Language-Team: Lao (https://app.transifex.com/opencloud-eu/teams/204053/lo/)\n"

View File

@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Matt, 2026\n"
"Language-Team: Polish (https://app.transifex.com/opencloud-eu/teams/204053/pl/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Mário Machado, 2025\n"
"Language-Team: Portuguese (https://app.transifex.com/opencloud-eu/teams/204053/pt/)\n"

View File

@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Lulufox, 2025\n"
"Language-Team: Russian (https://app.transifex.com/opencloud-eu/teams/204053/ru/)\n"

View File

@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Daniel Nylander <po@danielnylander.se>, 2025\n"
"Language-Team: Swedish (https://app.transifex.com/opencloud-eu/teams/204053/sv/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: YQS Yang, 2025\n"
"Language-Team: Chinese (https://app.transifex.com/opencloud-eu/teams/204053/zh/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Ivan Fustero, 2025\n"
"Language-Team: Catalan (https://app.transifex.com/opencloud-eu/teams/204053/ca/)\n"

View File

@@ -13,7 +13,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:59+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Jannik Stehle, 2026\n"
"Language-Team: German (https://app.transifex.com/opencloud-eu/teams/204053/de/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Efstathios Iosifidis <eiosifidis@gmail.com>, 2026\n"
"Language-Team: Greek (https://app.transifex.com/opencloud-eu/teams/204053/el/)\n"

View File

@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Alejandro Robles, 2025\n"
"Language-Team: Spanish (https://app.transifex.com/opencloud-eu/teams/204053/es/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Jiri Grönroos <jiri.gronroos@iki.fi>, 2025\n"
"Language-Team: Finnish (https://app.transifex.com/opencloud-eu/teams/204053/fi/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: eric_G <junk.eg@free.fr>, 2025\n"
"Language-Team: French (https://app.transifex.com/opencloud-eu/teams/204053/fr/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: mitibor, 2026\n"
"Language-Team: Hungarian (https://app.transifex.com/opencloud-eu/teams/204053/hu/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: idoet <idoet@protonmail.ch>, 2025\n"
"Language-Team: Indonesian (https://app.transifex.com/opencloud-eu/teams/204053/id/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Simone Pagano, 2025\n"
"Language-Team: Italian (https://app.transifex.com/opencloud-eu/teams/204053/it/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: iikaka88, 2025\n"
"Language-Team: Japanese (https://app.transifex.com/opencloud-eu/teams/204053/ja/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: gapho shin, 2025\n"
"Language-Team: Korean (https://app.transifex.com/opencloud-eu/teams/204053/ko/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: BoneNI, 2026\n"
"Language-Team: Lao (https://app.transifex.com/opencloud-eu/teams/204053/lo/)\n"

View File

@@ -13,7 +13,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Radoslaw Posim, 2025\n"
"Language-Team: Polish (https://app.transifex.com/opencloud-eu/teams/204053/pl/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Mário Machado, 2025\n"
"Language-Team: Portuguese (https://app.transifex.com/opencloud-eu/teams/204053/pt/)\n"

View File

@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Lulufox, 2025\n"
"Language-Team: Russian (https://app.transifex.com/opencloud-eu/teams/204053/ru/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Davis Kaza, 2025\n"
"Language-Team: Swedish (https://app.transifex.com/opencloud-eu/teams/204053/sv/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: LinkinWires <darkinsonic13@gmail.com>, 2025\n"
"Language-Team: Ukrainian (https://app.transifex.com/opencloud-eu/teams/204053/uk/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: YQS Yang, 2025\n"
"Language-Team: Chinese (https://app.transifex.com/opencloud-eu/teams/204053/zh/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Ivan Fustero, 2026\n"
"Language-Team: Catalan (https://app.transifex.com/opencloud-eu/teams/204053/ca/)\n"

View File

@@ -14,7 +14,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:59+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Jannik Stehle, 2026\n"
"Language-Team: German (https://app.transifex.com/opencloud-eu/teams/204053/de/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Efstathios Iosifidis <eiosifidis@gmail.com>, 2026\n"
"Language-Team: Greek (https://app.transifex.com/opencloud-eu/teams/204053/el/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Elías Martín, 2025\n"
"Language-Team: Spanish (https://app.transifex.com/opencloud-eu/teams/204053/es/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Jiri Grönroos <jiri.gronroos@iki.fi>, 2026\n"
"Language-Team: Finnish (https://app.transifex.com/opencloud-eu/teams/204053/fi/)\n"

View File

@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Benoît Aguesse, 2026\n"
"Language-Team: French (https://app.transifex.com/opencloud-eu/teams/204053/fr/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: mitibor, 2026\n"
"Language-Team: Hungarian (https://app.transifex.com/opencloud-eu/teams/204053/hu/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Simone Broglia, 2025\n"
"Language-Team: Italian (https://app.transifex.com/opencloud-eu/teams/204053/it/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: iikaka88, 2025\n"
"Language-Team: Japanese (https://app.transifex.com/opencloud-eu/teams/204053/ja/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: gapho shin, 2025\n"
"Language-Team: Korean (https://app.transifex.com/opencloud-eu/teams/204053/ko/)\n"

View File

@@ -14,7 +14,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Matt, 2026\n"
"Language-Team: Polish (https://app.transifex.com/opencloud-eu/teams/204053/pl/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Mário Machado, 2025\n"
"Language-Team: Portuguese (https://app.transifex.com/opencloud-eu/teams/204053/pt/)\n"

View File

@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Danila Kononov, 2026\n"
"Language-Team: Russian (https://app.transifex.com/opencloud-eu/teams/204053/ru/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Daniel Nylander <po@danielnylander.se>, 2025\n"
"Language-Team: Swedish (https://app.transifex.com/opencloud-eu/teams/204053/sv/)\n"

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-11 09:50+0000\n"
"POT-Creation-Date: 2026-07-01 23:15+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: YQS Yang, 2025\n"
"Language-Team: Chinese (https://app.transifex.com/opencloud-eu/teams/204053/zh/)\n"

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

@@ -143,6 +143,26 @@
// This provides a substantial CPU savings in benchmarks, because the
// "index rewriting" passes become much cheaper from not having to rehash
// every child's index.
//
// # Deletion and memory reuse
//
// In the original design, deletion was implemented as simply unlinking
// the node at the root of the section-to-be-deleted, and the Go GC
// would naturally collect the dead nodes eventually. This made the
// deletion operation extremely fast, at the expense of increasing GC
// work down the road. For some workloads, that was fine. Others, less so.
//
// The new design uses a sync.Pool for recycling EditTree nodes,
// reducing allocation overheads when there's a lot of churn and in-place
// editing happening. (ast.Terms held inside an EditTree are not pooled:
// most are borrowed from caller-owned data, so returning them would be
// unsafe.)
//
// The reclamation now happens eagerly at Dispose time, and recursively
// clears state and returns child nodes to the pool. Because of the Delete
// API returning the deleted node, callers are responsible for returning
// the node back to the pool.
package edittree
import (
@@ -178,20 +198,31 @@ func NewEditTree(term *ast.Term) *EditTree {
return initForTerm(&EditTree{}, term)
}
// Creates a new EditTree instance from the package-internal sync.Pool.
// Callers should call edittree.Dispose() to return the node when done.
func EditTreeFromPool(term *ast.Term) *EditTree {
if term == nil {
return nil
}
return initForTerm(editTreePool.Get(), term)
}
// Recursively reclaims the tree nodes that are part of this EditTree.
func Dispose(e *EditTree) {
if e != nil {
editTreePool.Put(e.Reset())
editTreePool.Put(e.Reclaim())
}
}
func (e *EditTree) Reset() *EditTree {
// Reclaim is a recursive equivalent of Reset methods
// seen in other APIs.
func (e *EditTree) Reclaim() *EditTree {
e.value = nil
clear(e.childKeys)
clear(e.childScalarValues)
e.disposeCompositeChildren()
clear(e.childCompositeValues)
e.eliminated = e.eliminated.Clear()
@@ -200,6 +231,17 @@ func (e *EditTree) Reset() *EditTree {
return e
}
// disposeCompositeChildren returns every non-nil composite child to the
// pool. It does not clear or nil the map itself; callers are responsible
// for that depending on context (clear for in-place reuse, nil for tear-down).
func (e *EditTree) disposeCompositeChildren() {
for _, child := range e.childCompositeValues {
if child != nil {
Dispose(child)
}
}
}
func initForTerm(tree *EditTree, term *ast.Term) *EditTree {
tree.value = term
@@ -261,8 +303,8 @@ func (e *EditTree) getKeyHash(key *ast.Term) (int, bool) {
}
//gcassert:inline
func isComposite(t *ast.Term) bool {
switch t.Value.(type) {
func isComposite(v ast.Value) bool {
switch v.(type) {
case ast.Object, ast.Set, *ast.Array:
return true
default:
@@ -312,11 +354,12 @@ func (e *EditTree) Insert(key, value *ast.Term) (*EditTree, error) {
return nil, fmt.Errorf("set key %v does not equal value to be inserted %v", key, value)
}
// We only collapse this Set-typed node if a composite type is involved.
if isComposite(key) {
if isComposite(key.Value) {
// TODO: Investigate re-rendering *only* the immediate composite children.
e.value = e.Render()
clear(e.childKeys)
clear(e.childScalarValues)
e.disposeCompositeChildren()
clear(e.childCompositeValues)
}
return e.unsafeInsertSet(key, value), nil
@@ -338,12 +381,16 @@ func (e *EditTree) Insert(key, value *ast.Term) (*EditTree, error) {
func (e *EditTree) unsafeInsertObject(key, value *ast.Term) *EditTree {
keyHash, found := e.getKeyHash(key)
if found {
// Reclaim child nodes if present.
if child, ok := e.childCompositeValues[keyHash]; ok && child != nil {
Dispose(child)
}
e.deleteChildValue(keyHash)
}
e.setChildKey(keyHash, key)
child := NewEditTree(value)
if isComposite(value) {
child := EditTreeFromPool(value)
if isComposite(value.Value) {
e.setChildCompositeValue(keyHash, child)
} else {
e.setChildScalarValue(keyHash, value)
@@ -352,13 +399,17 @@ func (e *EditTree) unsafeInsertObject(key, value *ast.Term) *EditTree {
}
func (e *EditTree) unsafeInsertSet(key, value *ast.Term) *EditTree {
child := NewEditTree(value)
child := EditTreeFromPool(value)
keyHash, found := e.getKeyHash(key)
if found {
// Reclaim child nodes if present.
if child, ok := e.childCompositeValues[keyHash]; ok && child != nil {
Dispose(child)
}
e.deleteChildValue(keyHash)
}
e.setChildKey(keyHash, key)
if isComposite(value) {
if isComposite(value.Value) {
e.setChildCompositeValue(keyHash, child)
} else {
e.setChildScalarValue(keyHash, value)
@@ -405,8 +456,8 @@ func (e *EditTree) unsafeInsertArray(idx int, value *ast.Term) *EditTree {
e.insertions.Insert(1, idx)
}
child := NewEditTree(value)
if isComposite(value) {
child := EditTreeFromPool(value)
if isComposite(value.Value) {
e.setChildCompositeValue(idx, child)
} else {
e.setChildScalarValue(idx, value)
@@ -435,7 +486,7 @@ func (e *EditTree) Delete(key *ast.Term) (*EditTree, error) {
}
e.setChildKey(keyHash, key)
e.setChildScalarValue(keyHash, nil)
return NewEditTree(child), nil
return EditTreeFromPool(child), nil
}
if child, ok := e.childCompositeValues[keyHash]; ok {
if child == nil {
@@ -460,12 +511,13 @@ func (e *EditTree) Delete(key *ast.Term) (*EditTree, error) {
return e.fallbackDelete(key)
case ast.Set:
// We only collapse this Set-typed node if a composite type is involved.
if isComposite(key) {
if isComposite(key.Value) {
// TODO: Investigate re-rendering *only* the immediate composite children.
collapsed := e.Render()
e.value = collapsed
clear(e.childKeys)
clear(e.childScalarValues)
e.disposeCompositeChildren()
clear(e.childCompositeValues)
} else {
keyHash, found := e.getKeyHash(key)
@@ -478,7 +530,7 @@ func (e *EditTree) Delete(key *ast.Term) (*EditTree, error) {
if key.Equal(child) {
e.setChildKey(keyHash, key)
e.setChildScalarValue(keyHash, nil)
return NewEditTree(child), nil
return EditTreeFromPool(child), nil
}
}
}
@@ -495,6 +547,12 @@ func (e *EditTree) Delete(key *ast.Term) (*EditTree, error) {
return nil, errors.New("index for array delete out of bounds")
}
// Capture the deleted composite child (if any) before the maps are
// rewritten, so the caller can recycle it. Scalar children and
// "original" array elements eliminated via bleed-through have no
// separate EditTree node to recycle, so nil is returned in those cases.
deletedChild := e.childCompositeValues[idx]
// Collect insertion indexes above the delete site for rewriting.
rewritesScalars := []int{}
rewritesComposites := []int{}
@@ -542,7 +600,7 @@ func (e *EditTree) Delete(key *ast.Term) (*EditTree, error) {
}
// Delete element from insertions array, bump bit-vec over by 1.
e.insertions.Delete(idx)
return e, nil
return deletedChild, nil
default:
// Catch all primitive types.
return nil, fmt.Errorf("expected composite type, found value: %v (type: %T)", e.value.Value, e.value.Value)
@@ -574,7 +632,10 @@ func findIndexOfNthZero(n int, bv *bitvector.BitVector) (int, bool) {
}
// Helper function for sets/objects when the key isn't present in either
// child map.
// child map. It unpacks the key/value pair from the stored term on
// the EditTree node. For composite types, it then acts as if Unfold then
// Delete were called for the key. For scalars, it skips the Unfold step,
// and uses the scalar child value map directly to record the deletion.
func (e *EditTree) fallbackDelete(key *ast.Term) (*EditTree, error) {
// get ref from pool
rptr := refPool.Get(1)
@@ -589,12 +650,12 @@ func (e *EditTree) fallbackDelete(key *ast.Term) (*EditTree, error) {
}
keyHash, _ := e.getKeyHash(key)
e.setChildKey(keyHash, key)
if isComposite(ast.NewTerm(value)) {
if isComposite(value) {
e.setChildCompositeValue(keyHash, nil)
} else {
e.setChildScalarValue(keyHash, nil)
}
return NewEditTree(ast.NewTerm(value)), nil
return EditTreeFromPool(ast.NewTerm(value)), nil
}
// Unfurls a chain of EditTree nodes down a given path, or else returns an error.
@@ -618,7 +679,7 @@ func (e *EditTree) Unfold(path ast.Ref) (*EditTree, error) {
if term == nil {
return nil, fmt.Errorf("cannot unfold the already deleted scalar node for key %v", key)
}
child := NewEditTree(term)
child := EditTreeFromPool(term)
return child.Unfold(path[1:])
}
if child, ok := e.childCompositeValues[keyHash]; ok {
@@ -652,16 +713,17 @@ func (e *EditTree) Unfold(path ast.Ref) (*EditTree, error) {
// traversal, we have to collapse the tree beneath this node,
// so that we can accurately unfold it again for an update,
// once we know that the key we care about is present.
if isComposite(key) {
if isComposite(key.Value) {
collapsed := e.Render()
e.value = collapsed
clear(e.childKeys)
clear(e.childScalarValues)
e.disposeCompositeChildren()
clear(e.childCompositeValues)
} else {
if keyHash, found := e.getKeyHash(key); found {
if term, ok := e.childScalarValues[keyHash]; ok {
child := NewEditTree(term)
child := EditTreeFromPool(term)
return child.Unfold(path[1:])
}
}
@@ -682,7 +744,7 @@ func (e *EditTree) Unfold(path ast.Ref) (*EditTree, error) {
return nil, err
}
if term, ok := e.childScalarValues[idx]; ok {
child := NewEditTree(term)
child := EditTreeFromPool(term)
return child.Unfold(path[1:])
}
if child, ok := e.childCompositeValues[idx]; ok {
@@ -858,6 +920,7 @@ func (e *EditTree) InsertAtPath(path ast.Ref, value *ast.Term) (*EditTree, error
e.value = value
clear(e.childKeys)
clear(e.childScalarValues)
e.disposeCompositeChildren()
clear(e.childCompositeValues)
if v, ok := value.Value.(*ast.Array); ok {
@@ -888,6 +951,7 @@ func (e *EditTree) DeleteAtPath(path ast.Ref) (*EditTree, error) {
e.value = nil
e.childKeys = nil
e.childScalarValues = nil
e.disposeCompositeChildren()
e.childCompositeValues = nil
e.eliminated = nil
e.insertions = nil
@@ -967,10 +1031,11 @@ func (e *EditTree) Exists(path ast.Ref) bool {
// traversal, we have to collapse the tree beneath this node,
// so that we can accurately unfold it again for an update,
// once we know that the key we care about is present.
if isComposite(key) {
if isComposite(key.Value) {
e.value = e.Render()
clear(e.childKeys)
clear(e.childScalarValues)
e.disposeCompositeChildren()
clear(e.childCompositeValues)
} else if keyHash, found := e.getKeyHash(key); found {
if _, ok := e.childScalarValues[keyHash]; ok {

View File

@@ -161,7 +161,18 @@ func (p *Planner) buildFunctrie() error {
}
func (p *Planner) planRules(rules []*ast.Rule) (string, error) {
// We know the rules with closer to the root (shorter static path) are ordered first.
// We sort rules, first by ref length, and then using the
// Ref.Compare method to break ties. This yields a stable
// sorting order for the slice of rules to be planned.
sort.Slice(rules, func(i, j int) bool {
li, lj := len(rules[i].Ref()), len(rules[j].Ref())
if li != lj {
return li > lj
}
return rules[i].Ref().Compare(rules[j].Ref()) < 0
})
// We know the rules that are closer to the root (shorter static path) are ordered first.
pathRef := rules[0].Ref()
// figure out what our rules' collective name/path is:
@@ -262,12 +273,6 @@ func (p *Planner) planRules(rules []*ast.Rule) (string, error) {
var defaultRule *ast.Rule
var ruleLoc *location.Location
// We sort rules by ref length, to ensure that when merged, we can detect conflicts when one
// rule attempts to override values (deep and shallow) defined by another rule.
sort.Slice(rules, func(i, j int) bool {
return len(rules[i].Ref()) > len(rules[j].Ref())
})
// Generate function blocks for rules.
for i := range rules {

View File

@@ -1,36 +0,0 @@
// Copyright 2020 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
// Package ref implements internal helpers for references
package ref
import (
"errors"
"github.com/open-policy-agent/opa/v1/ast"
"github.com/open-policy-agent/opa/v1/storage"
"github.com/open-policy-agent/opa/v1/util"
)
// ParseDataPath returns a ref from the slash separated path s rooted at data.
// All path segments are treated as identifier strings.
func ParseDataPath(s string) (ast.Ref, error) {
path, ok := storage.ParsePath(util.WithPrefix(s, "/"))
if !ok {
return nil, errors.New("invalid path")
}
return path.Ref(ast.DefaultRootDocument), nil
}
// ArrayPath will take an ast.Array and build an ast.Ref using the ast.Terms in the Array
func ArrayPath(a *ast.Array) ast.Ref {
ref := make(ast.Ref, 0, a.Len())
a.Foreach(func(term *ast.Term) {
ref = append(ref, term)
})
return ref
}

View File

@@ -33,4 +33,4 @@ func Write(ctx context.Context, store storage.Store, txn storage.Transaction) er
}
// UserAgent defines the current OPA instances User-Agent default header value.
var UserAgent = fmt.Sprintf("Open Policy Agent/%s (%s, %s)", version.Version, runtime.GOOS, runtime.GOARCH)
var UserAgent = fmt.Sprintf("Open-Policy-Agent/%s (%s, %s)", version.Version, runtime.GOOS, runtime.GOARCH)

View File

@@ -9,11 +9,8 @@ import (
"fmt"
"maps"
"net/url"
"runtime"
"slices"
"strings"
"sync"
"weak"
"github.com/open-policy-agent/opa/internal/deepcopy"
astJSON "github.com/open-policy-agent/opa/v1/ast/json"
@@ -70,11 +67,10 @@ type (
}
AnnotationSet struct {
byRule map[*Rule][]*Annotations
byPackage map[int]*Annotations
byPath *annotationTreeNode
modules []*Module // Modules this set was constructed from
mergedLabels sync.Map // map[weak.Pointer[Rule]]*ruleLabelsEntry; lazily populated, entries cleaned up via runtime.AddCleanup when rules are GC'd
byRule map[*Rule][]*Annotations
byPackage map[int]*Annotations
byPath *annotationTreeNode
modules []*Module // Modules this set was constructed from
}
annotationTreeNode struct {
@@ -234,6 +230,10 @@ func (a *Annotations) MarshalJSON() ([]byte, error) {
data["schemas"] = a.Schemas
}
if a.Compile != nil {
data["compile"] = a.Compile
}
if len(a.Custom) > 0 {
data["custom"] = a.Custom
}
@@ -960,42 +960,19 @@ func (as *AnnotationSet) Chain(rule *Rule) AnnotationsRefSet {
return refs
}
// ruleLabelsEntry caches the merged labels and dedup key for a single rule.
type ruleLabelsEntry struct {
labels map[string]any
key string
}
// MergedLabels returns the inner-scope-wins merged labels for the given rule
// along with a stable string suitable for content-based deduplication. The
// result is computed once per rule and cached on the AnnotationSet; the cache
// entry is dropped automatically when the rule is garbage-collected.
//
// labels is nil when the rule has no labels anywhere in its annotation chain;
// in that case key is the empty string.
// along with a stable JSON string suitable for content-based deduplication.
// labels is nil when the rule has no labels anywhere in its annotation chain.
func (as *AnnotationSet) MergedLabels(rule *Rule) (labels map[string]any, key string) {
if as == nil {
return nil, ""
}
k := weak.Make(rule)
if v, ok := as.mergedLabels.Load(k); ok {
e := v.(*ruleLabelsEntry)
return e.labels, e.key
labels = mergeChainLabels(as.Chain(rule))
if len(labels) > 0 {
b, _ := json.Marshal(labels)
key = string(b)
}
merged := mergeChainLabels(as.Chain(rule))
e := &ruleLabelsEntry{labels: merged}
if len(merged) > 0 {
b, _ := json.Marshal(merged)
e.key = string(b)
}
actual, loaded := as.mergedLabels.LoadOrStore(k, e)
if !loaded {
// k is a weak.Pointer (value type) — it does not keep rule alive, so
// the cleanup will fire once the rule becomes unreachable elsewhere.
runtime.AddCleanup(rule, func(k weak.Pointer[Rule]) { as.mergedLabels.Delete(k) }, k)
}
e = actual.(*ruleLabelsEntry)
return e.labels, e.key
return labels, key
}
// mergeChainLabels folds labels from a rule's annotation chain with inner-wins

View File

@@ -3662,11 +3662,12 @@ func (b *Builtin) Call(operands ...*Term) *Term {
// Ref returns a Ref that refers to the built-in function.
func (b *Builtin) Ref() Ref {
parts := strings.Split(b.Name, ".")
ref := make(Ref, len(parts))
ref[0] = VarTerm(parts[0])
for i := 1; i < len(parts); i++ {
ref[i] = InternedTerm(parts[i])
numParts := strings.Count(b.Name, ".") + 1
curr, remaining, ok := strings.Cut(b.Name, ".")
ref := append(make(Ref, 0, numParts), VarTerm(curr))
for ok {
curr, remaining, ok = strings.Cut(remaining, ".")
ref = append(ref, InternedTerm(curr))
}
return ref
}

View File

@@ -38,7 +38,6 @@ import (
// is empty.
// Other comparisons are consistent but not defined.
func Compare(a, b any) int {
if t, ok := a.(*Term); ok {
if t == nil {
a = nil
@@ -77,7 +76,7 @@ func Compare(a, b any) int {
switch a := a.(type) {
case *Not:
b := b.(*Not)
return Compare(a.Body, b.Body)
return a.Compare(b)
case Null:
return 0
case Boolean:
@@ -120,25 +119,13 @@ func Compare(a, b any) int {
return a.Compare(b.(Set))
case *ArrayComprehension:
b := b.(*ArrayComprehension)
if cmp := Compare(a.Term, b.Term); cmp != 0 {
return cmp
}
return a.Body.Compare(b.Body)
return a.Compare(b)
case *ObjectComprehension:
b := b.(*ObjectComprehension)
if cmp := Compare(a.Key, b.Key); cmp != 0 {
return cmp
}
if cmp := Compare(a.Value, b.Value); cmp != 0 {
return cmp
}
return a.Body.Compare(b.Body)
return a.Compare(b)
case *SetComprehension:
b := b.(*SetComprehension)
if cmp := Compare(a.Term, b.Term); cmp != 0 {
return cmp
}
return a.Body.Compare(b.Body)
return a.Compare(b)
case Call:
return termSliceCompare(a, b.(Call))
case *Expr:
@@ -173,14 +160,8 @@ func Compare(a, b any) int {
panic(fmt.Sprintf("illegal value: %T", a))
}
type termSlice []*Term
func (s termSlice) Less(i, j int) bool { return Compare(s[i].Value, s[j].Value) < 0 }
func (s termSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s termSlice) Len() int { return len(s) }
func sortOrder(x any) int {
switch x.(type) {
func valueSortOrder(v Value) int {
switch v.(type) {
case Null:
return 0
case Boolean:
@@ -209,6 +190,28 @@ func sortOrder(x any) int {
return 12
case Call:
return 13
case *Not:
return 111
}
return 10000000
}
func valueTypeCompare[A, B Value](a A, b B) int {
sortA := valueSortOrder(a)
sortB := valueSortOrder(b)
if sortA < sortB {
return -1
} else if sortB < sortA {
return 1
}
return 0
}
func sortOrder(x any) int {
switch v := x.(type) {
case Value:
return valueSortOrder(v)
case Args:
return 14
case *Expr:
@@ -223,8 +226,6 @@ func sortOrder(x any) int {
return 104
case *With:
return 110
case *Not:
return 111
case *Head:
return 120
case Body:
@@ -294,7 +295,7 @@ func rulesCompare(a, b []*Rule) int {
func termSliceCompare(a, b []*Term) int {
minLen := min(len(b), len(a))
for i := range minLen {
if cmp := Compare(a[i], b[i]); cmp != 0 {
if cmp := a[i].Value.Compare(b[i].Value); cmp != 0 {
return cmp
}
}
@@ -309,7 +310,7 @@ func termSliceCompare(a, b []*Term) int {
func withSliceCompare(a, b []*With) int {
minLen := min(len(b), len(a))
for i := range minLen {
if cmp := Compare(a[i], b[i]); cmp != 0 {
if cmp := a[i].Compare(b[i]); cmp != 0 {
return cmp
}
}

View File

@@ -5540,6 +5540,7 @@ func resolveRefsInExpr(globals map[Var]*usedRef, ignore *declaredVarStack, expr
cpy.Terms = &Not{
Body: resolveRefsInBody(globals, ignore, ts.Body),
ExplicitBody: ts.ExplicitBody,
Location: ts.Location,
}
case *LogicalAnd:
cpy.Terms = &LogicalAnd{

View File

@@ -271,6 +271,17 @@ func HasInternedIntNumberTerm(i int) bool {
return i >= -1 && i < len(intNumberTerms)
}
// InternedStringTermFromNumber returns an interned string term whose value is
// the number's decimal string representation, if that string is interned.
// Returns nil otherwise.
//
// This is an optimisation for the base-10 format_int fast path: for numbers
// like Number("99") the string we want to output is exactly string(n), so we
// can skip strconv.ParseInt and look up the result directly.
func InternedStringTermFromNumber(n Number) *Term {
return internedStringTerms[string(n)]
}
// Returns an interned string term representing the integer value i, if
// interned. If not, creates a new StringTerm for the integer value.
func InternedIntegerString(i int) *Term {

View File

@@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"unicode/utf8"
astJSON "github.com/open-policy-agent/opa/v1/ast/json"
"github.com/open-policy-agent/opa/v1/util"
@@ -29,6 +30,9 @@ func NewLocation(text []byte, file string, row int, col int) *Location {
// Equal checks if two locations are equal to each other.
func (loc *Location) Equal(other *Location) bool {
if loc == nil || other == nil {
return loc == other
}
return loc.File == other.File &&
loc.Row == other.Row &&
loc.Col == other.Col &&
@@ -89,6 +93,37 @@ func (loc *Location) StringLength() (n int) {
return n
}
// HasFile reports whether loc carries a non-empty File. Safe to call on a
// nil receiver.
func (loc *Location) HasFile() bool {
return loc != nil && loc.File != ""
}
// End returns the (row, col) one past the last rune of loc.Text — an
// exclusive end matching the scanner's offset calculation, so [Start, End)
// covers the text. Columns are counted per rune. Returns (Row, Col) for
// empty text and (0, 0) for a nil receiver.
func (loc *Location) End() (row, col int) {
if loc == nil {
return 0, 0
}
if len(loc.Text) == 0 {
return loc.Row, loc.Col
}
row = loc.Row + bytes.Count(loc.Text, []byte{'\n'})
col = loc.Col
lastLine := loc.Text
if row != loc.Row {
col = 1
lastLine = loc.Text[bytes.LastIndex(loc.Text, []byte{'\n'})+1:]
}
return row, col + utf8.RuneCount(lastLine)
}
// Compare returns -1, 0, or 1 to indicate if this loc is less than, equal to,
// or greater than the other. Comparison is performed on the file, row, and
// column of the Location (but not on the text.) Nil locations are greater than

View File

@@ -1221,6 +1221,13 @@ func (p *Parser) parseLiteral() (expr *Expr) {
if expr != nil {
loc.Text = p.s.Text(offset, p.s.lastEnd)
expr.SetLoc(loc)
// For implicit not-body wrapping (future.keywords.not), propagate
// the outer `not <op>` span to the inner expression.
if not, ok := expr.Terms.(*Not); ok && !not.ExplicitBody {
for _, inner := range not.Body {
inner.SetLoc(loc)
}
}
}
}()
@@ -1260,14 +1267,19 @@ func (p *Parser) parseLiteral() (expr *Expr) {
if tok == tokens.Dot || tok == tokens.LBrack {
p.s.tok = tokens.Ident
return p.parseLiteralExpr(false)
return p.parseLiteralExpr(false, nil)
}
}
negated := isNegatedExpression(p)
var notLoc *Location
negated := isNegated(p)
if negated {
notLoc = p.s.Loc()
p.scan()
}
if negated && p.notBodies && p.s.tok == tokens.LBrace {
nb := p.parseNotBody()
nb := p.parseNotBody(notLoc)
if nb != nil && p.s.tok == tokens.With {
if nb.With = p.parseWith(); nb.With == nil {
@@ -1292,7 +1304,7 @@ func (p *Parser) parseLiteral() (expr *Expr) {
}
return p.parseEvery()
default:
return p.parseLiteralExpr(negated)
return p.parseLiteralExpr(negated, notLoc)
}
}
@@ -1308,7 +1320,7 @@ func (p *Parser) isAllowedRefKeywordStr(s string) bool {
return false
}
func (p *Parser) parseLiteralExpr(negated bool) *Expr {
func (p *Parser) parseLiteralExpr(negated bool, notLoc *Location) *Expr {
startOffset := p.s.loc.Offset
startLoc := p.s.Loc()
s := p.save()
@@ -1343,7 +1355,14 @@ func (p *Parser) parseLiteralExpr(negated bool) *Expr {
// Move 'with' statement to outer not expr
w := expr.With
expr.With = nil
expr = NewExpr(&Not{Body: NewBody(expr), Location: p.s.Loc()})
var spanned *Location
if notLoc != nil {
// Extend the location to also include the 'not ' prefix
spanned = p.extendLoc(notLoc)
}
expr = NewExpr(&Not{Body: NewBody(expr), Location: spanned}).SetLocation(spanned)
expr.With = w
} else {
expr.Negated = negated
@@ -1506,18 +1525,19 @@ func (p *Parser) parseSome() *Expr {
return NewExpr(decl).SetLocation(decl.Location)
}
func (p *Parser) parseNotBody() *Expr {
loc := p.s.Loc()
p.scan()
func (p *Parser) parseNotBody(notLoc *Location) *Expr {
p.scan() // consume `{`
body := p.parseBody(tokens.RBrace)
if body == nil {
return nil
}
p.scan()
p.scan() // consume `}`
not := &Not{Body: body, ExplicitBody: true, Location: loc}
return NewExpr(not).SetLocation(loc)
// Extend the location to also include the 'not ' prefix
spanned := p.extendLoc(notLoc)
not := &Not{Body: body, ExplicitBody: true, Location: spanned}
return NewExpr(not).SetLocation(spanned)
}
// logicalKeywordsActive reports whether the scanner currently treats `and` or
@@ -1549,19 +1569,19 @@ func (p *Parser) parseLogicalOrChain(lhsBody Body, lhsExplicit bool, lhsLoc *Loc
}
lhsBody = NewBody(andExpr)
lhsExplicit = false
lhsLoc = andExpr.Location
}
for p.s.tok == tokens.LogicalOr {
p.scan()
rhsBody, rhsExplicit := p.parseLogicalOperand()
rhsBody, rhsExplicit, rhsLoc := p.parseLogicalOperand()
if rhsBody == nil {
return nil
}
// RHS may extend into a higher-precedence `and`-chain.
if p.s.tok == tokens.LogicalAnd {
rhsLoc := rhsBody[0].Location
andExpr := p.parseLogicalAndChain(rhsBody, rhsExplicit, rhsLoc)
if andExpr == nil {
return nil
@@ -1570,16 +1590,18 @@ func (p *Parser) parseLogicalOrChain(lhsBody Body, lhsExplicit bool, lhsLoc *Loc
rhsExplicit = false
}
exprLoc := p.extendLoc(lhsLoc)
node := &LogicalOr{
Lhs: lhsBody,
Rhs: rhsBody,
ExplicitLhs: lhsExplicit,
ExplicitRhs: rhsExplicit,
Location: lhsLoc,
Location: exprLoc,
}
wrapper := NewExpr(node).SetLocation(lhsLoc)
wrapper := NewExpr(node).SetLocation(exprLoc)
lhsBody = NewBody(wrapper)
lhsExplicit = false
lhsLoc = exprLoc
}
return lhsBody[0]
@@ -1600,72 +1622,81 @@ func (p *Parser) parseLogicalAndChain(lhsBody Body, lhsExplicit bool, lhsLoc *Lo
for p.s.tok == tokens.LogicalAnd {
p.scan()
rhsBody, rhsExplicit := p.parseLogicalOperand()
rhsBody, rhsExplicit, _ := p.parseLogicalOperand()
if rhsBody == nil {
return nil
}
exprLoc := p.extendLoc(lhsLoc)
node := &LogicalAnd{
Lhs: lhsBody,
Rhs: rhsBody,
ExplicitLhs: lhsExplicit,
ExplicitRhs: rhsExplicit,
Location: lhsLoc,
Location: exprLoc,
}
wrapper := NewExpr(node).SetLocation(lhsLoc)
wrapper := NewExpr(node).SetLocation(exprLoc)
lhsBody = NewBody(wrapper)
lhsExplicit = false
lhsLoc = exprLoc
}
return lhsBody[0]
}
func isNegatedExpression(p *Parser) bool {
if p.s.tok == tokens.Not {
// Distinguish the `not` keyword from a ref like `not.x`.
s := p.save()
p.scanWS()
tok := p.s.tok
p.restore(s)
if tok != tokens.Dot && tok != tokens.LBrack {
p.scan()
return true
}
// extendLoc returns a copy of start with Text re-spanned from start.Offset
// to the scanner's current lastEnd.
func (p *Parser) extendLoc(start *Location) *Location {
cpy := *start
cpy.Text = p.s.Text(start.Offset, p.s.lastEnd)
return &cpy
}
func isNegated(p *Parser) bool {
if p.s.tok != tokens.Not {
return false
}
return false
// Distinguish the `not` keyword from a ref like `not.x`.
s := p.save()
p.scanWS()
tok := p.s.tok
p.restore(s)
return tok != tokens.Dot && tok != tokens.LBrack
}
// parseLogicalOperand parses a single operand of an `and`/`or` expression.
// Returns the operand body and whether it was parsed from an explicit `{...}`
// body. Returns (nil, false) on parse error.
func (p *Parser) parseLogicalOperand() (Body, bool) {
func (p *Parser) parseLogicalOperand() (Body, bool, *Location) {
if p.s.tok == tokens.LBrace {
loc := p.s.Loc()
p.scan()
body := p.parseBody(tokens.RBrace)
if body == nil {
return nil, false
return nil, false, nil
}
p.scan()
_ = loc
return body, true
return body, true, loc
}
negated := isNegatedExpression(p)
var notLoc *Location
negated := isNegated(p)
if negated {
notLoc = p.s.Loc()
p.scan()
}
if negated && p.notBodies && p.s.tok == tokens.LBrace {
nb := p.parseNotBody()
nb := p.parseNotBody(notLoc)
if nb == nil {
return nil, false
return nil, false, nil
}
return NewBody(nb), false
return NewBody(nb), false, nb.Location
}
startOffset := p.s.loc.Offset
startLoc := p.s.Loc()
expr := p.parseExpr()
if expr == nil {
return nil, false
return nil, false, nil
}
if expr.Location == nil {
@@ -1675,13 +1706,14 @@ func (p *Parser) parseLogicalOperand() (Body, bool) {
if negated && p.notBodies {
// Don't attach any existing 'with' statements, they belong to the and/or, not the negated expression.
notNode := &Not{Body: NewBody(expr), Location: expr.Location}
expr = NewExpr(notNode).SetLocation(expr.Location)
spanned := p.extendLoc(notLoc)
notNode := &Not{Body: NewBody(expr), Location: spanned}
expr = NewExpr(notNode).SetLocation(spanned)
} else if negated {
expr.Negated = true
}
return NewBody(expr), false
return NewBody(expr), false, expr.Location
}
func (p *Parser) parseEvery() *Expr {

View File

@@ -562,7 +562,7 @@ func (imp *Import) Compare(other *Import) int {
} else if other == nil {
return 1
}
if cmp := Compare(imp.Path, other.Path); cmp != 0 {
if cmp := imp.Path.Value.Compare(other.Path.Value); cmp != 0 {
return cmp
}
@@ -906,10 +906,10 @@ func (head *Head) Compare(other *Head) int {
} else if !head.Assign && other.Assign {
return 1
}
if cmp := Compare(head.Args, other.Args); cmp != 0 {
if cmp := termSliceCompare(head.Args, other.Args); cmp != 0 {
return cmp
}
if cmp := Compare(head.Reference, other.Reference); cmp != 0 {
if cmp := termSliceCompare(head.Reference, other.Reference); cmp != 0 {
return cmp
}
if cmp := VarCompare(head.Name, other.Name); cmp != 0 {
@@ -1218,7 +1218,6 @@ func (expr *Expr) Equal(other *Expr) bool {
// Otherwise, the expression terms are compared normally. If both expressions
// have the same terms, the modifiers are compared.
func (expr *Expr) Compare(other *Expr) int {
if expr == nil {
if other == nil {
return 0
@@ -1252,7 +1251,7 @@ func (expr *Expr) Compare(other *Expr) int {
switch t := expr.Terms.(type) {
case *Term:
if cmp := Compare(t.Value, other.Terms.(*Term).Value); cmp != 0 {
if cmp := t.Value.Compare(other.Terms.(*Term).Value); cmp != 0 {
return cmp
}
case []*Term:
@@ -1268,7 +1267,7 @@ func (expr *Expr) Compare(other *Expr) int {
return cmp
}
case *Not:
if cmp := Compare(t, other.Terms.(*Not)); cmp != 0 {
if cmp := t.Compare(other.Terms.(*Not)); cmp != 0 {
return cmp
}
case *LogicalAnd:

View File

@@ -857,12 +857,10 @@ func (num Number) Equal(other Value) bool {
// Compare compares num to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (num Number) Compare(other Value) int {
// Optimize for the common case, as calling Compare allocates on heap.
if otherNum, yes := other.(Number); yes {
return NumberCompare(num, otherNum)
}
return Compare(num, other)
return valueTypeCompare(num, other)
}
// Find returns the current value or a not found error.
@@ -974,7 +972,7 @@ func (str String) Compare(other Value) int {
return 1
}
return Compare(str, other)
return valueTypeCompare(str, other)
}
// Find returns the current value or a not found error.
@@ -1059,7 +1057,7 @@ func (ts *TemplateString) Compare(other Value) int {
return 0
}
return Compare(ts, other)
return valueTypeCompare(ts, other)
}
func (ts *TemplateString) Find(path Ref) (Value, error) {
@@ -1171,7 +1169,7 @@ func (v Var) Compare(other Value) int {
if otherVar, ok := other.(Var); ok {
return strings.Compare(string(v), string(otherVar))
}
return Compare(v, other)
return valueTypeCompare(v, other)
}
// Find returns the current value or a not found error.
@@ -1364,8 +1362,7 @@ func (ref Ref) Compare(other Value) int {
if o, ok := other.(Ref); ok {
return termSliceCompare(ref, o)
}
return Compare(ref, other)
return valueTypeCompare(ref, other)
}
// Find returns the current value or a "not found" error.
@@ -1634,16 +1631,7 @@ func (arr *Array) Compare(other Value) int {
return termSliceCompare(arr.elems, b.elems)
}
sortA := sortOrder(arr)
sortB := sortOrder(other)
if sortA < sortB {
return -1
} else if sortB < sortA {
return 1
}
return Compare(arr, other)
return valueTypeCompare(arr, other)
}
// Find returns the value at the index or an out-of-range error.
@@ -1927,15 +1915,11 @@ func (s *set) sortedKeys() []*Term {
// Compare compares s to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (s *set) Compare(other Value) int {
o1 := sortOrder(s)
o2 := sortOrder(other)
if o1 < o2 {
return -1
} else if o1 > o2 {
return 1
if t, ok := other.(*set); ok {
return slices.CompareFunc(s.sortedKeys(), t.sortedKeys(), TermValueCompare)
}
t := other.(*set)
return termSliceCompare(s.sortedKeys(), t.sortedKeys())
return valueTypeCompare(s, other)
}
// Find returns the set or dereferences the element itself.
@@ -2204,12 +2188,8 @@ func (l *lazyObj) force() Object {
}
func (l *lazyObj) Compare(other Value) int {
o1 := sortOrder(l)
o2 := sortOrder(other)
if o1 < o2 {
return -1
} else if o2 < o1 {
return 1
if c := valueTypeCompare(l, other); c != 0 {
return c
}
return l.force().Compare(other)
}
@@ -2414,12 +2394,8 @@ func (obj *object) Compare(other Value) int {
if x, ok := other.(*lazyObj); ok {
other = x.force()
}
o1 := sortOrder(obj)
o2 := sortOrder(other)
if o1 < o2 {
return -1
} else if o2 < o1 {
return 1
if c := valueTypeCompare(obj, other); c != 0 {
return c
}
a := obj
b := other.(*object)
@@ -2431,27 +2407,14 @@ func (obj *object) Compare(other Value) int {
minLen = len(bkeys)
}
for i := range minLen {
keysCmp := Compare(akeys[i].key, bkeys[i].key)
if keysCmp < 0 {
return -1
if c := akeys[i].key.Value.Compare(bkeys[i].key.Value); c != 0 {
return c
}
if keysCmp > 0 {
return 1
}
valA := akeys[i].value
valB := bkeys[i].value
valCmp := Compare(valA, valB)
if valCmp != 0 {
return valCmp
if c := akeys[i].value.Value.Compare(bkeys[i].value.Value); c != 0 {
return c
}
}
if len(akeys) < len(bkeys) {
return -1
}
if len(bkeys) < len(akeys) {
return 1
}
return 0
return len(akeys) - len(bkeys)
}
// Find returns the value at the key or undefined.
@@ -2506,7 +2469,7 @@ func KeyHashEqual(x, y Value) bool {
}
}
return Compare(x, y) == 0
return x.Compare(y) == 0
}
// Hash returns the hash code for the Value.
@@ -2906,13 +2869,19 @@ func (ac *ArrayComprehension) Copy() *ArrayComprehension {
// Equal returns true if ac is equal to other.
func (ac *ArrayComprehension) Equal(other Value) bool {
return Compare(ac, other) == 0
return ac.Compare(other) == 0
}
// Compare compares ac to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (ac *ArrayComprehension) Compare(other Value) int {
return Compare(ac, other)
if bc, ok := other.(*ArrayComprehension); ok {
if c := ac.Term.Value.Compare(bc.Term.Value); c != 0 {
return c
}
return ac.Body.Compare(bc.Body)
}
return valueTypeCompare(ac, other)
}
// Find returns the current value or a not found error.
@@ -2967,13 +2936,22 @@ func (oc *ObjectComprehension) Copy() *ObjectComprehension {
// Equal returns true if oc is equal to other.
func (oc *ObjectComprehension) Equal(other Value) bool {
return Compare(oc, other) == 0
return oc.Compare(other) == 0
}
// Compare compares oc to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (oc *ObjectComprehension) Compare(other Value) int {
return Compare(oc, other)
if bc, ok := other.(*ObjectComprehension); ok {
if c := oc.Key.Value.Compare(bc.Key.Value); c != 0 {
return c
}
if c := oc.Value.Value.Compare(bc.Value.Value); c != 0 {
return c
}
return oc.Body.Compare(bc.Body)
}
return valueTypeCompare(oc, other)
}
// Find returns the current value or a not found error.
@@ -3025,13 +3003,19 @@ func (sc *SetComprehension) Copy() *SetComprehension {
// Equal returns true if sc is equal to other.
func (sc *SetComprehension) Equal(other Value) bool {
return Compare(sc, other) == 0
return sc.Compare(other) == 0
}
// Compare compares sc to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (sc *SetComprehension) Compare(other Value) int {
return Compare(sc, other)
if oc, ok := other.(*SetComprehension); ok {
if c := sc.Term.Value.Compare(oc.Term.Value); c != 0 {
return c
}
return sc.Body.Compare(oc.Body)
}
return valueTypeCompare(sc, other)
}
// Find returns the current value or a not found error.
@@ -3074,7 +3058,10 @@ func (c Call) Copy() Call {
// Compare compares c to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (c Call) Compare(other Value) int {
return Compare(c, other)
if oc, ok := other.(Call); ok {
return termSliceCompare(c, oc)
}
return valueTypeCompare(c, other)
}
// Find returns the current value or a not found error.

View File

@@ -130,6 +130,9 @@ func NewFile(name, hash, alg string) FileInfo {
// Manifest represents the manifest from a bundle. The manifest may contain
// metadata such as the bundle revision.
//
// Schema mirror: manifest.proto + manifest_proto_test.go. Update both when
// adding/renaming/removing fields; never reuse a proto field number.
type Manifest struct {
Revision string `json:"revision"`
Roots *[]string `json:"roots,omitempty"`
@@ -1259,12 +1262,16 @@ func (m *Manifest) numericRegoVersionForFile(path string) (*int, error) {
if len(m.FileRegoVersions) != len(m.compiledFileRegoVersions) {
m.compiledFileRegoVersions = make([]fileRegoVersion, 0, len(m.FileRegoVersions))
for pattern, v := range m.FileRegoVersions {
// Compile patterns in sorted key order. The behaviour for overlapping
// patterns is documented as undefined, however we ensure the ordering
// of which patterns are used will be deterministic by sorting the
// keys before iterating over the patterns.
for _, pattern := range util.KeysSorted(m.FileRegoVersions) {
compiled, err := glob.Compile(pattern)
if err != nil {
return nil, fmt.Errorf("failed to compile glob pattern %s: %s", pattern, err)
}
m.compiledFileRegoVersions = append(m.compiledFileRegoVersions, fileRegoVersion{compiled, v})
m.compiledFileRegoVersions = append(m.compiledFileRegoVersions, fileRegoVersion{compiled, m.FileRegoVersions[pattern]})
}
}

View File

@@ -0,0 +1,112 @@
// Copyright 2026 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
edition = "2023";
package opa.bundle.v1;
import "google/protobuf/struct.proto";
option go_package = "github.com/open-policy-agent/opa/v1/bundle/v1pb";
option java_multiple_files = true;
// Manifest mirrors `bundle.Manifest` in v1/bundle/bundle.go.
message Manifest {
// Bundle revision string.
string revision = 1;
// Root paths the bundle owns.
repeated string roots = 2;
// Wasm resolvers attached to the bundle. JSON key is `wasm`.
repeated WasmResolver wasm = 3;
// Global Rego version for the bundle. Currently 0 (RegoV0) or 1 (RegoV1).
int32 rego_version = 4;
// Per-file Rego version overrides keyed by file path.
map<string, int32> file_rego_versions = 5;
// Free-form metadata object. Modeled as `Struct` because the Go field
// is `map[string]any`.
google.protobuf.Struct metadata = 6;
}
// WasmResolver mirrors `bundle.WasmResolver` in v1/bundle/bundle.go.
message WasmResolver {
// Entrypoint policy ref this resolver targets.
string entrypoint = 1;
// Path to the wasm module within the bundle.
string module = 2;
// Rego annotations attached to the entrypoint.
repeated Annotations annotations = 3;
}
// Annotations mirrors `ast.Annotations` in v1/ast/annotations.go.
message Annotations {
string scope = 1;
string title = 2;
bool entrypoint = 3;
string description = 4;
repeated string organizations = 5;
repeated RelatedResourceAnnotation related_resources = 6;
repeated AuthorAnnotation authors = 7;
repeated SchemaAnnotation schemas = 8;
CompileAnnotation compile = 9;
// `custom` and `labels` are `map[string]any` in Go — genuinely
// free-form, so `Struct` is the right model.
google.protobuf.Struct custom = 10;
google.protobuf.Struct labels = 11;
Location location = 12;
}
// SchemaAnnotation mirrors `ast.SchemaAnnotation`. Path/Schema are
// `ast.Ref` in Go (a list of terms); the wire form is the canonical
// dotted ref string (e.g. `data.foo.bar`). Modeling the term tree
// faithfully would pull most of the AST into this schema and isn't
// worth the cost for annotations.
message SchemaAnnotation {
string path = 1;
string schema = 2;
// `*any` on the Go side — a parsed JSON Schema document or any
// JSON value. `Value` (not `Struct`) because the top level may be
// a scalar, list, or null, not just an object.
google.protobuf.Value definition = 3;
}
// CompileAnnotation mirrors `ast.CompileAnnotation`. Refs are the
// canonical dotted form; see SchemaAnnotation for the trade-off.
message CompileAnnotation {
repeated string unknowns = 1;
string mask_rule = 2;
}
// AuthorAnnotation mirrors `ast.AuthorAnnotation`.
message AuthorAnnotation {
string name = 1;
string email = 2;
}
// RelatedResourceAnnotation mirrors `ast.RelatedResourceAnnotation`.
// `Ref` is a `url.URL` in Go, serialized to its `String()` form.
message RelatedResourceAnnotation {
string ref = 1;
string description = 2;
}
// Location mirrors `ast.Location` (= `location.Location`). Only the
// File/Row/Col triple is wire-relevant; `Text`, `Offset`, and `Tabs`
// are tagged `json:"-"` and intentionally absent.
//
// Distinct from `ir.Location`, which is promoted onto the `Stmt`
// envelope in plan.proto. The two are independent Go types.
message Location {
string file = 1;
int32 row = 2;
int32 col = 3;
}

View File

@@ -780,7 +780,7 @@ func erasePolicies(ctx context.Context, store storage.Store, txn storage.Transac
}
getRegoVersion := func(modId string) (ast.RegoVersion, bool) {
info, ok := modulesInfo[modId]
info, ok := modulesInfo[strings.TrimPrefix(modId, "/")]
if !ok {
return ast.RegoUndefined, false
}

View File

@@ -963,7 +963,12 @@ func (w *writer) writeExpr(expr *ast.Expr, comments []*ast.Comment) ([]*ast.Comm
}
withs := expr.With
lastRow := expr.Location.Row
// Compare against the row where the expression's terms end (its
// closing-bracket row), not where they begin. For a multi-line expression
// the lone leading `with` sits on the closing-bracket line, which differs
// from the start row, and comparing against the start would eject it onto
// its own indented line (see issue #8804).
lastRow := exprTermsEndRow(expr)
// Print on same row if already there, otherwise increase indent a print remaining
if withs[0].Location.Row == lastRow {
@@ -997,6 +1002,28 @@ func (w *writer) writeExpr(expr *ast.Expr, comments []*ast.Comment) ([]*ast.Comm
return comments, nil
}
// exprTermsEndRow returns the row of the last source line occupied by the
// expression's own terms, ignoring any trailing `with` modifiers. For a
// single-line expression this equals expr.Location.Row; for a multi-line one
// (e.g. a wrapped function call ending in `)` or an `every` block ending in
// `}`) it is the closing-bracket row. expr.Location.Text spans the `with`
// clauses too, so they are trimmed off via the first `with`'s offset before
// the remaining term text is measured.
func exprTermsEndRow(expr *ast.Expr) int {
loc := expr.Location
if loc == nil {
return 0
}
text := loc.Text
if len(expr.With) > 0 && expr.With[0].Location != nil {
if off := expr.With[0].Location.Offset - loc.Offset; off > 0 && off <= len(text) {
text = text[:off]
}
}
text = bytes.TrimRight(text, " \t\r\n")
return loc.Row + bytes.Count(text, []byte{'\n'})
}
func (w *writer) writeSomeDecl(decl *ast.SomeDecl, comments []*ast.Comment) ([]*ast.Comment, error) {
var err error
comments, err = w.insertComments(comments, decl.Location)
@@ -1212,7 +1239,13 @@ func (w *writer) writeWith(with *ast.With, comments []*ast.Comment, indented boo
w.write(" as ")
comments, err = w.writeTerm(with.Value, comments)
if err != nil {
return comments, err
// An unexpectedCommentError from writeTerm signals that it fell
// back to writing the term's original unformatted text — the value
// was written successfully, so don't abort the surrounding chain
// of `with` clauses (issue #8765).
if !errors.As(err, &unexpectedCommentError{}) {
return comments, err
}
}
return comments, nil
}
@@ -1869,9 +1902,13 @@ func (w *writer) writeIterable(elements []any, last *ast.Location, close *ast.Lo
return nil, err
}
hasBoundaryComment := false
// If there are comments within the single line, don't collapse it and keep it as-is
// Return an error so that writeTerm will write the original formatting
if len(lines) == 1 {
if w.beforeEnd != nil {
hasBoundaryComment = w.beforeEnd.Location.Row >= last.Row && w.beforeEnd.Location.Row <= close.Row
}
for _, c := range comments {
if c.Location.Row > last.Row && c.Location.Row < close.Row {
return comments, unexpectedCommentError{
@@ -1879,10 +1916,14 @@ func (w *writer) writeIterable(elements []any, last *ast.Location, close *ast.Lo
newCommentRow: c.Location.Row,
}
}
if c.Location.Row == last.Row || c.Location.Row == close.Row {
hasBoundaryComment = true
}
}
}
if len(lines) > 1 {
isMultiline := len(lines) > 1 || (len(lines) == 1 && len(elements) > 0 && close.Row > last.Row && !hasBoundaryComment)
if isMultiline {
w.delayBeforeEnd()
w.startMultilineSeq()
}
@@ -1904,7 +1945,7 @@ func (w *writer) writeIterable(elements []any, last *ast.Location, close *ast.Lo
return nil, err
}
if len(lines) > 1 {
if isMultiline {
w.write(",")
w.endLine()
comments, err = w.insertComments(comments, close)

View File

@@ -6,6 +6,10 @@
//
// The IR specifies an imperative execution model for Rego policies similar to a
// query plan in traditional databases.
//
// Schema mirror: plan.proto + plan_proto_test.go. Update both when
// adding/renaming/removing fields or Stmt/Val kinds; never reuse a proto
// field number or oneof case.
package ir
import (

View File

@@ -0,0 +1,364 @@
// Copyright 2026 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
edition = "2023";
package opa.ir.v1;
option go_package = "github.com/open-policy-agent/opa/v1/ir/v1pb";
option java_multiple_files = true;
// Policy mirrors `ir.Policy` in v1/ir/ir.go.
message Policy {
Static static = 1;
Plans plans = 2;
Funcs funcs = 3;
}
// Static mirrors `ir.Static` in v1/ir/ir.go.
message Static {
repeated StringConst strings = 1;
repeated BuiltinFunc builtin_funcs = 2;
repeated StringConst files = 3;
}
// BuiltinFunc mirrors `ir.BuiltinFunc` in v1/ir/ir.go.
//
// `ir.BuiltinFunc.Decl` (the function's `types.Function` signature) is
// intentionally not modeled. Consumers that execute plans need their
// own builtin registry to dispatch host-language implementations, and
// that registry is the source of truth for signatures — bundling
// `Decl` here would be redundant and prone to drift.
message BuiltinFunc {
string name = 1;
}
// Plans mirrors `ir.Plans` in v1/ir/ir.go.
message Plans {
repeated Plan plans = 1;
}
// Funcs mirrors `ir.Funcs` in v1/ir/ir.go.
message Funcs {
repeated Func funcs = 1;
}
// Func mirrors `ir.Func` in v1/ir/ir.go.
//
// Each parameter and the return slot are local-variable indices; see
// `ir.Local` in v1/ir/ir.go.
message Func {
string name = 1;
repeated int32 params = 2;
// The local that holds the function's return value. Renamed from
// `return` (the Go field is `Func.Return Local`, JSON-tagged `return`)
// to avoid colliding with a reserved keyword in many target languages
// when this proto is fed to protoc plugins. The JSON wire form
// continues to use `return`; only the proto-side identifier differs.
int32 result = 3;
repeated Block blocks = 4;
repeated string path = 5;
}
// Plan mirrors `ir.Plan` in v1/ir/ir.go.
message Plan {
string name = 1;
repeated Block blocks = 2;
}
// Block mirrors `ir.Block` in v1/ir/ir.go.
message Block {
repeated Stmt stmts = 1;
}
// StringConst mirrors `ir.StringConst` in v1/ir/ir.go.
message StringConst {
string value = 1;
}
// Operand mirrors `ir.Operand` in v1/ir/ir.go. The `value` field is a
// polymorphic `Val` union; see the `Val` message below.
message Operand {
Val value = 1;
}
// Val mirrors the `ir.Val` interface in v1/ir/ir.go. Each oneof case
// corresponds to a concrete `Val` implementation; the case names match
// the JSON discriminator strings emitted by `*Operand.MarshalJSON`.
//
// Case-number assignments are a stability commitment. New cases must
// be added with the next unused number; existing numbers must never
// be repurposed.
message Val {
oneof kind {
bool bool = 1;
int32 local = 2;
int32 string_index = 3;
}
}
// Stmt mirrors the `ir.Stmt` interface in v1/ir/ir.go. Every Stmt carries
// the source-location triple (file, col, row) on this envelope; the body
// messages below describe only the kind-specific payload.
//
// On the Go side, `ir.Location` is embedded into every concrete Stmt
// implementation, so `encoding/json` flattens File/Col/Row into the
// emitted JSON body. The proto promotes those fields to the envelope
// because that's both more idiomatic protobuf and lets every body
// message start its own field numbering at 1.
//
// Case-number assignments (437) are a stability commitment. Field
// numbers 13 are reserved for the location triple. New cases must be
// added with the next unused number; existing numbers must never be
// repurposed.
message Stmt {
int32 file = 1;
int32 col = 2;
int32 row = 3;
oneof kind {
ArrayAppendStmt array_append_stmt = 4;
AssignIntStmt assign_int_stmt = 5;
AssignVarOnceStmt assign_var_once_stmt = 6;
AssignVarStmt assign_var_stmt = 7;
BlockStmt block_stmt = 8;
BreakStmt break_stmt = 9;
CallDynamicStmt call_dynamic_stmt = 10;
CallStmt call_stmt = 11;
DotStmt dot_stmt = 12;
EqualStmt equal_stmt = 13;
IsArrayStmt is_array_stmt = 14;
IsDefinedStmt is_defined_stmt = 15;
IsObjectStmt is_object_stmt = 16;
IsSetStmt is_set_stmt = 17;
IsUndefinedStmt is_undefined_stmt = 18;
LenStmt len_stmt = 19;
MakeArrayStmt make_array_stmt = 20;
MakeNullStmt make_null_stmt = 21;
MakeNumberIntStmt make_number_int_stmt = 22;
MakeNumberRefStmt make_number_ref_stmt = 23;
MakeObjectStmt make_object_stmt = 24;
MakeSetStmt make_set_stmt = 25;
NopStmt nop_stmt = 26;
NotEqualStmt not_equal_stmt = 27;
NotStmt not_stmt = 28;
ObjectInsertOnceStmt object_insert_once_stmt = 29;
ObjectInsertStmt object_insert_stmt = 30;
ObjectMergeStmt object_merge_stmt = 31;
ResetLocalStmt reset_local_stmt = 32;
ResultSetAddStmt result_set_add_stmt = 33;
ReturnLocalStmt return_local_stmt = 34;
ScanStmt scan_stmt = 35;
SetAddStmt set_add_stmt = 36;
WithStmt with_stmt = 37;
}
}
// Stmt body messages start their own field numbering at 1; the
// source-location triple lives on the parent `Stmt` envelope.
// ArrayAppendStmt mirrors `ir.ArrayAppendStmt` in v1/ir/ir.go.
message ArrayAppendStmt {
Operand value = 1;
int32 array = 2;
}
// AssignIntStmt mirrors `ir.AssignIntStmt` in v1/ir/ir.go.
message AssignIntStmt {
int64 value = 1;
int32 target = 2;
}
// AssignVarOnceStmt mirrors `ir.AssignVarOnceStmt` in v1/ir/ir.go.
message AssignVarOnceStmt {
Operand source = 1;
int32 target = 2;
}
// AssignVarStmt mirrors `ir.AssignVarStmt` in v1/ir/ir.go.
message AssignVarStmt {
Operand source = 1;
int32 target = 2;
}
// BlockStmt mirrors `ir.BlockStmt` in v1/ir/ir.go.
message BlockStmt {
repeated Block blocks = 1;
}
// BreakStmt mirrors `ir.BreakStmt` in v1/ir/ir.go.
message BreakStmt {
uint32 index = 1;
}
// CallDynamicStmt mirrors `ir.CallDynamicStmt` in v1/ir/ir.go.
message CallDynamicStmt {
repeated int32 args = 1;
int32 result = 2;
repeated Operand path = 3;
}
// CallStmt mirrors `ir.CallStmt` in v1/ir/ir.go.
message CallStmt {
// Renamed from `func` (Go field `CallStmt.Func`, JSON-tagged `func`)
// to avoid colliding with reserved keywords in target languages,
// mirroring the Func.return → result rename above.
string function = 1;
repeated Operand args = 2;
int32 result = 3;
}
// DotStmt mirrors `ir.DotStmt` in v1/ir/ir.go.
message DotStmt {
Operand source = 1;
Operand key = 2;
int32 target = 3;
}
// EqualStmt mirrors `ir.EqualStmt` in v1/ir/ir.go.
message EqualStmt {
Operand a = 1;
Operand b = 2;
}
// IsArrayStmt mirrors `ir.IsArrayStmt` in v1/ir/ir.go.
message IsArrayStmt {
Operand source = 1;
}
// IsDefinedStmt mirrors `ir.IsDefinedStmt` in v1/ir/ir.go.
message IsDefinedStmt {
int32 source = 1;
}
// IsObjectStmt mirrors `ir.IsObjectStmt` in v1/ir/ir.go.
message IsObjectStmt {
Operand source = 1;
}
// IsSetStmt mirrors `ir.IsSetStmt` in v1/ir/ir.go.
message IsSetStmt {
Operand source = 1;
}
// IsUndefinedStmt mirrors `ir.IsUndefinedStmt` in v1/ir/ir.go.
message IsUndefinedStmt {
int32 source = 1;
}
// LenStmt mirrors `ir.LenStmt` in v1/ir/ir.go.
message LenStmt {
Operand source = 1;
int32 target = 2;
}
// MakeArrayStmt mirrors `ir.MakeArrayStmt` in v1/ir/ir.go.
message MakeArrayStmt {
int32 capacity = 1;
int32 target = 2;
}
// MakeNullStmt mirrors `ir.MakeNullStmt` in v1/ir/ir.go.
message MakeNullStmt {
int32 target = 1;
}
// MakeNumberIntStmt mirrors `ir.MakeNumberIntStmt` in v1/ir/ir.go.
message MakeNumberIntStmt {
int64 value = 1;
int32 target = 2;
}
// MakeNumberRefStmt mirrors `ir.MakeNumberRefStmt` in v1/ir/ir.go.
//
// The Go field is named `Index` (no `json` tag). The historical JSON
// shape emitted both `index` and `Index`; the canonical key going
// forward is `index` and the deprecated `Index` alias will be removed
// in a future major release. This proto models only the canonical
// `index` field.
message MakeNumberRefStmt {
int32 index = 1;
int32 target = 2;
}
// MakeObjectStmt mirrors `ir.MakeObjectStmt` in v1/ir/ir.go.
message MakeObjectStmt {
int32 target = 1;
}
// MakeSetStmt mirrors `ir.MakeSetStmt` in v1/ir/ir.go.
message MakeSetStmt {
int32 target = 1;
}
// NopStmt mirrors `ir.NopStmt` in v1/ir/ir.go.
message NopStmt {}
// NotEqualStmt mirrors `ir.NotEqualStmt` in v1/ir/ir.go.
message NotEqualStmt {
Operand a = 1;
Operand b = 2;
}
// NotStmt mirrors `ir.NotStmt` in v1/ir/ir.go.
message NotStmt {
Block block = 1;
}
// ObjectInsertOnceStmt mirrors `ir.ObjectInsertOnceStmt` in v1/ir/ir.go.
message ObjectInsertOnceStmt {
Operand key = 1;
Operand value = 2;
int32 object = 3;
}
// ObjectInsertStmt mirrors `ir.ObjectInsertStmt` in v1/ir/ir.go.
message ObjectInsertStmt {
Operand key = 1;
Operand value = 2;
int32 object = 3;
}
// ObjectMergeStmt mirrors `ir.ObjectMergeStmt` in v1/ir/ir.go.
message ObjectMergeStmt {
int32 a = 1;
int32 b = 2;
int32 target = 3;
}
// ResetLocalStmt mirrors `ir.ResetLocalStmt` in v1/ir/ir.go.
message ResetLocalStmt {
int32 target = 1;
}
// ResultSetAddStmt mirrors `ir.ResultSetAddStmt` in v1/ir/ir.go.
message ResultSetAddStmt {
int32 value = 1;
}
// ReturnLocalStmt mirrors `ir.ReturnLocalStmt` in v1/ir/ir.go.
message ReturnLocalStmt {
int32 source = 1;
}
// ScanStmt mirrors `ir.ScanStmt` in v1/ir/ir.go.
message ScanStmt {
int32 source = 1;
int32 key = 2;
int32 value = 3;
Block block = 4;
}
// SetAddStmt mirrors `ir.SetAddStmt` in v1/ir/ir.go.
message SetAddStmt {
Operand value = 1;
int32 set = 2;
}
// WithStmt mirrors `ir.WithStmt` in v1/ir/ir.go.
message WithStmt {
int32 local = 1;
repeated int32 path = 2;
Operand value = 3;
Block block = 4;
}

View File

@@ -0,0 +1,87 @@
// Copyright 2026 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package logging
import (
"context"
"log/slog"
)
// AsSlogLogger returns a *slog.Logger that forwards log records to the given Logger.
// Structured fields are forwarded via WithFields; log levels map to the equivalent
// Logger methods. Groups are not supported and are ignored.
func AsSlogLogger(logger Logger) *slog.Logger {
return slog.New(slogHandler{logger: logger})
}
// AsSlogLoggerWithPinnedLevel returns a *slog.Logger that forwards all log records
// to the given Logger at the specified fixed level, ignoring the slog record's own level.
// This is useful when bridging a library that uses slog into OPA's logger at a fixed verbosity.
func AsSlogLoggerWithPinnedLevel(logger Logger, level Level) *slog.Logger {
return slog.New(slogHandler{logger: logger, pinnedLevel: &level})
}
type slogHandler struct {
logger Logger
pinnedLevel *Level
}
func (slogHandler) Enabled(_ context.Context, _ slog.Level) bool { return true }
func (h slogHandler) Handle(_ context.Context, r slog.Record) error {
fields := make(map[string]any, r.NumAttrs())
r.Attrs(func(a slog.Attr) bool {
fields[a.Key] = a.Value.Any()
return true
})
l := h.logger.WithFields(fields)
if h.pinnedLevel != nil {
dispatchAtLevel(l, *h.pinnedLevel, r.Message)
return nil
}
// slog.Level is int; cascade from most-severe so custom levels between named ones route correctly.
switch {
case r.Level >= slog.LevelError:
l.Error(r.Message)
case r.Level >= slog.LevelWarn:
l.Warn(r.Message)
case r.Level >= slog.LevelInfo:
l.Info(r.Message)
default:
l.Debug(r.Message)
}
return nil
}
func (h slogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
fields := make(map[string]any, len(attrs))
for _, a := range attrs {
fields[a.Key] = a.Value.Any()
}
return slogHandler{logger: h.logger.WithFields(fields), pinnedLevel: h.pinnedLevel}
}
func (h slogHandler) WithGroup(_ string) slog.Handler { return h }
func dispatchAtLevel(l Logger, level Level, msg string) {
switch level {
case Error:
l.Error(msg)
case Warn:
l.Warn(msg)
case Info:
l.Info(msg)
default:
l.Debug(msg)
}
}

View File

@@ -2173,8 +2173,7 @@ func (r *Rego) parseQuery(queryImports []*ast.Import, m metrics.Metrics) (ast.Bo
func parserOptionsFromRegoVersionImport(imports []*ast.Import, popts ast.ParserOptions) (ast.ParserOptions, error) {
for _, imp := range imports {
path := imp.Path.Value.(ast.Ref)
if ast.Compare(path, ast.RegoV1CompatibleRef) == 0 {
if ast.RegoV1CompatibleRef.Compare(imp.Path.Value) == 0 {
popts.RegoVersion = ast.RegoV1
return popts, nil
}
@@ -3082,9 +3081,11 @@ func generateJSON(term *ast.Term, ectx *EvalContext) (any, error) {
}
func (r *Rego) planQuery(queries []ast.Body, evalQueryType queryType) (*ir.Policy, error) {
// We sort the list of module names here to ensure a deterministic
// output ordering for the planner.
modules := make([]*ast.Module, 0, len(r.compiler.Modules))
for _, module := range r.compiler.Modules {
modules = append(modules, module)
for _, name := range util.KeysSorted(r.compiler.Modules) {
modules = append(modules, r.compiler.Modules[name])
}
decls := make(map[string]*ast.Builtin, len(r.builtinDecls)+len(ast.BuiltinMap))

View File

@@ -127,7 +127,6 @@ func NewOperandErr(pos int, f string, a ...any) error {
// NewOperandTypeErr returns an operand error indicating the operand's type was wrong.
func NewOperandTypeErr(pos int, got ast.Value, expected ...string) error {
if len(expected) == 1 {
return NewOperandErr(pos, "must be %v but got %v", expected[0], ast.ValueName(got))
}
@@ -138,7 +137,6 @@ func NewOperandTypeErr(pos int, got ast.Value, expected ...string) error {
// NewOperandElementErr returns an operand error indicating an element in the
// composite operand was wrong.
func NewOperandElementErr(pos int, composite ast.Value, got ast.Value, expected ...string) error {
tpe := ast.ValueName(composite)
if len(expected) == 1 {
@@ -150,7 +148,6 @@ func NewOperandElementErr(pos int, composite ast.Value, got ast.Value, expected
// NewOperandEnumErr returns an operand error indicating a value was wrong.
func NewOperandEnumErr(pos int, expected ...string) error {
if len(expected) == 1 {
return NewOperandErr(pos, "must be %v", expected[0])
}
@@ -192,30 +189,27 @@ func BigIntOperand(x ast.Value, pos int) (*big.Int, error) {
// NumberOperand converts x to a number. If the cast fails, a descriptive error is
// returned.
func NumberOperand(x ast.Value, pos int) (ast.Number, error) {
n, ok := x.(ast.Number)
if !ok {
return ast.Number(""), NewOperandTypeErr(pos, x, "number")
if n, ok := x.(ast.Number); ok {
return n, nil
}
return n, nil
return ast.Number(""), NewOperandTypeErr(pos, x, "number")
}
// SetOperand converts x to a set. If the cast fails, a descriptive error is
// returned.
func SetOperand(x ast.Value, pos int) (ast.Set, error) {
s, ok := x.(ast.Set)
if !ok {
return nil, NewOperandTypeErr(pos, x, "set")
if s, ok := x.(ast.Set); ok {
return s, nil
}
return s, nil
return nil, NewOperandTypeErr(pos, x, "set")
}
// StringOperand returns x as [ast.String], or a descriptive error if the conversion fails.
func StringOperand(x ast.Value, pos int) (ast.String, error) {
s, ok := x.(ast.String)
if !ok {
return ast.String(""), NewOperandTypeErr(pos, x, "string")
if s, ok := x.(ast.String); ok {
return s, nil
}
return s, nil
return ast.String(""), NewOperandTypeErr(pos, x, "string")
}
// StringOperandByteSlice returns x a []byte, assuming x is [ast.String], or a descriptive error
@@ -229,24 +223,22 @@ func StringOperandByteSlice(x ast.Value, pos int) ([]byte, error) {
return util.StringToByteSlice(string(s)), nil
}
// ObjectOperand converts x to an object. If the cast fails, a descriptive
// ObjectOperand converts x to an object. If the conversion fails, a descriptive
// error is returned.
func ObjectOperand(x ast.Value, pos int) (ast.Object, error) {
o, ok := x.(ast.Object)
if !ok {
return nil, NewOperandTypeErr(pos, x, "object")
func ObjectOperand(x ast.Value, pos int) (o ast.Object, err error) {
if o, ok := x.(ast.Object); ok {
return o, nil
}
return o, nil
return nil, NewOperandTypeErr(pos, x, "object")
}
// ArrayOperand converts x to an array. If the cast fails, a descriptive
// ArrayOperand converts x to an array. If the conversion fails, a descriptive
// error is returned.
func ArrayOperand(x ast.Value, pos int) (*ast.Array, error) {
a, ok := x.(*ast.Array)
if !ok {
return nil, NewOperandTypeErr(pos, x, "array")
if a, ok := x.(*ast.Array); ok {
return a, nil
}
return a, nil
return nil, NewOperandTypeErr(pos, x, "array")
}
// NumberToFloat converts n to a big float.

View File

@@ -9,27 +9,27 @@ import "github.com/open-policy-agent/opa/v1/ast"
type compareFunc func(a, b ast.Value) bool
func compareGreaterThan(a, b ast.Value) bool {
return ast.Compare(a, b) > 0
return a.Compare(b) > 0
}
func compareGreaterThanEq(a, b ast.Value) bool {
return ast.Compare(a, b) >= 0
return a.Compare(b) >= 0
}
func compareLessThan(a, b ast.Value) bool {
return ast.Compare(a, b) < 0
return a.Compare(b) < 0
}
func compareLessThanEq(a, b ast.Value) bool {
return ast.Compare(a, b) <= 0
return a.Compare(b) <= 0
}
func compareNotEq(a, b ast.Value) bool {
return ast.Compare(a, b) != 0
return a.Compare(b) != 0
}
func compareEq(a, b ast.Value) bool {
return ast.Compare(a, b) == 0
return a.Compare(b) == 0
}
func builtinCompare(cmp compareFunc) BuiltinFunc {

View File

@@ -496,8 +496,7 @@ func (e *eval) evalStep(iter evalIterator) error {
})
}
case *ast.Term:
// generateVar inlined here to avoid extra allocations in hot path
rterm := ast.VarTerm(e.fmtVarTerm())
rterm := ast.VarTerm(e.fmtVar())
if e.partial() {
e.inliningControl.PushDisable(rterm.Value, true)
@@ -536,9 +535,8 @@ func (e *eval) evalStep(iter evalIterator) error {
case *ast.Not:
en := evalNot{
e: e,
not: terms,
expr: expr,
e: e,
not: terms,
}
err = en.eval(func(e *eval) error {
defined = true
@@ -575,8 +573,7 @@ func (e *eval) evalStep(iter evalIterator) error {
})
}
case *ast.Term:
// generateVar inlined here to avoid extra allocations in hot path
rterm := ast.VarTerm(e.fmtVarTerm())
rterm := ast.VarTerm(e.fmtVar())
err = e.unify(terms, rterm, func() error {
if e.saveSet != nil && e.saveSet.Contains(rterm, e.bindings) {
return e.saveExpr(ast.NewExpr(rterm), e.bindings, func() error {
@@ -600,9 +597,8 @@ func (e *eval) evalStep(iter evalIterator) error {
case *ast.Not:
en := evalNot{
e: e,
not: terms,
expr: expr,
e: e,
not: terms,
}
err = en.eval(func(e *eval) error {
return iter(e)
@@ -617,7 +613,7 @@ func (e *eval) evalStep(iter evalIterator) error {
// Single-purpose fmt.Sprintf replacement for generating variable names with only
// one allocation performed instead of 4, and in 1/3 the time.
func (e *eval) fmtVarTerm() string {
func (e *eval) fmtVar() string {
buf := make([]byte, 0, len(e.genvarprefix)+util.NumDigitsUint(e.queryID)+util.NumDigitsInt(e.index)+7)
buf = append(buf, e.genvarprefix...)
@@ -4208,36 +4204,69 @@ func isIterableValue(x ast.Value) bool {
}
func (e *evalEvery) save(iter unifyIterator) error {
return e.e.saveExpr(e.plug(e.expr), e.e.bindings, iter)
plugged, err := e.plug(e.expr)
if err != nil {
return err
}
return e.e.saveExpr(plugged, e.e.bindings, iter)
}
func (e *evalEvery) plug(expr *ast.Expr) *ast.Expr {
func (e *evalEvery) plug(expr *ast.Expr) (*ast.Expr, error) {
cpy := expr.Copy()
every := cpy.Terms.(*ast.Every)
for i := range every.Body {
switch t := every.Body[i].Terms.(type) {
case *ast.Term:
every.Body[i].Terms = e.e.bindings.PlugNamespaced(t, e.e.caller.bindings)
case []*ast.Term:
for j := 1; j < len(t); j++ { // don't plug operator, t[0]
t[j] = e.e.bindings.PlugNamespaced(t[j], e.e.caller.bindings)
}
case *ast.Every:
every.Body[i] = e.plug(every.Body[i])
}
if err := e.plugBody(every.Body); err != nil {
return nil, err
}
every.Key = e.e.bindings.PlugNamespaced(every.Key, e.e.caller.bindings)
every.Value = e.e.bindings.PlugNamespaced(every.Value, e.e.caller.bindings)
every.Domain = e.e.bindings.PlugNamespaced(every.Domain, e.e.caller.bindings)
cpy.Terms = every
return cpy
return cpy, nil
}
func (e *evalEvery) plugBody(body ast.Body) error {
for i := range body {
switch t := body[i].Terms.(type) {
case *ast.Term:
plugged, err := e.plugTerm(t)
if err != nil {
return err
}
body[i].Terms = plugged
case []*ast.Term:
for j := 1; j < len(t); j++ { // don't plug operator, t[0]
plugged, err := e.plugTerm(t[j])
if err != nil {
return err
}
t[j] = plugged
}
case *ast.Every:
plugged, err := e.plug(body[i])
if err != nil {
return err
}
body[i] = plugged
case *ast.Not:
if err := e.plugBody(t.Body); err != nil {
return err
}
}
}
return nil
}
func (e *evalEvery) plugTerm(t *ast.Term) (*ast.Term, error) {
if ast.IsComprehension(t.Value) {
return e.e.amendComprehension(t, e.e.bindings)
}
return e.e.bindings.PlugNamespaced(t, e.e.caller.bindings), nil
}
type evalNot struct {
e *eval
not *ast.Not
expr *ast.Expr
e *eval
not *ast.Not
}
func (e evalNot) eval(iter evalIterator) error {

View File

@@ -286,12 +286,16 @@ func applyPatches(source *ast.Term, operations *ast.Array) (*ast.Term, error) {
return nil, err
}
case "remove":
if _, err = et.DeleteAtPath(path); err != nil {
if deleted, err := et.DeleteAtPath(path); err != nil {
return nil, err
} else if deleted != et {
edittree.Dispose(deleted) // Reclaim only on success path.
}
case "replace":
if _, err = et.DeleteAtPath(path); err != nil {
if deleted, err := et.DeleteAtPath(path); err != nil {
return nil, err
} else if deleted != et {
edittree.Dispose(deleted) // Reclaim only on success path.
}
value := object.Get(ast.InternedTerm("value"))
if value == nil {
@@ -314,8 +318,10 @@ func applyPatches(source *ast.Term, operations *ast.Array) (*ast.Term, error) {
if err != nil {
return nil, err
}
if _, err = et.DeleteAtPath(from); err != nil {
if deleted, err := et.DeleteAtPath(from); err != nil {
return nil, err
} else if deleted != et {
edittree.Dispose(deleted) // Reclaim dead original structure.
}
if _, err = et.InsertAtPath(path, chunk); err != nil {
return nil, err

View File

@@ -5,7 +5,8 @@
package topdown
import (
"github.com/open-policy-agent/opa/internal/ref"
"cmp"
"github.com/open-policy-agent/opa/v1/ast"
"github.com/open-policy-agent/opa/v1/topdown/builtins"
)
@@ -24,16 +25,11 @@ func builtinObjectUnion(_ BuiltinContext, operands []*ast.Term, iter func(*ast.T
if objA.Len() == 0 {
return iter(operands[1])
}
if objB.Len() == 0 {
return iter(operands[0])
}
if objA.Compare(objB) == 0 {
if objB.Len() == 0 || objA.Compare(objB) == 0 {
return iter(operands[0])
}
r := mergeWithOverwrite(objA, objB)
return iter(ast.NewTerm(r))
return iter(ast.NewTerm(mergeWithOverwrite(objA, objB)))
}
func builtinObjectUnionN(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
@@ -42,6 +38,21 @@ func builtinObjectUnionN(_ BuiltinContext, operands []*ast.Term, iter func(*ast.
return err
}
n := arr.Len()
if n == 0 {
return iter(ast.InternedEmptyObject)
}
first := arr.Elem(0)
obj, ok := first.Value.(ast.Object)
if !ok {
return builtins.NewOperandElementErr(1, arr, first.Value, "object")
}
if n == 1 {
return iter(first)
}
// Because we need merge-with-overwrite behavior, we can iterate
// back-to-front, and get a mostly correct set of key assignments that
// give us the "last assignment wins, with merges" behavior we want.
@@ -54,20 +65,23 @@ func builtinObjectUnionN(_ BuiltinContext, operands []*ast.Term, iter func(*ast.
// Want Output: {"a": {"c": 3}}
// First pass: count total keys for pre-allocation
totalSize := 0
for i := range arr.Len() {
o, ok := arr.Elem(i).Value.(ast.Object)
totalSize := obj.Len()
for i := 1; i < n; i++ {
elem := arr.Elem(i)
o, ok := elem.Value.(ast.Object)
if !ok {
return builtins.NewOperandElementErr(1, arr, arr.Elem(i).Value, "object")
return builtins.NewOperandElementErr(1, arr, elem.Value, "object")
}
totalSize += o.Len()
}
result := ast.NewObjectWithCapacity(totalSize)
frozenKeys := make(map[*ast.Term]struct{}, totalSize)
for i := arr.Len() - 1; i >= 0; i-- {
o := arr.Elem(i).Value.(ast.Object) // Already validated above
mergewithOverwriteInPlace(result, o, frozenKeys)
for i := n - 1; i >= 0; i-- {
if o := arr.Elem(i).Value.(ast.Object); o.Len() > 0 {
mergewithOverwriteInPlace(result, o, frozenKeys)
}
}
return iter(ast.NewTerm(result))
@@ -126,34 +140,27 @@ func builtinObjectFilter(_ BuiltinContext, operands []*ast.Term, iter func(*ast.
}
func builtinObjectGet(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
object, err := builtins.ObjectOperand(operands[0].Value, 1)
// silly micro optimization: initial ref to last item avoids
// later bounds checks as 1 and 0 then known to be valid indices
defaultValue, path, curr := operands[2], operands[1], operands[0]
object, err := builtins.ObjectOperand(curr.Value, 1)
if err != nil {
return err
}
// if the get key is not an array, attempt to get the top level key for the operand value in the object
path, ok := operands[1].Value.(*ast.Array)
arr, ok := path.Value.(*ast.Array)
if !ok {
if ret := object.Get(operands[1]); ret != nil {
return iter(ret)
return iter(cmp.Or(object.Get(path), defaultValue))
}
for i := range arr.Len() {
if curr = curr.Get(arr.Elem(i)); curr == nil {
break
}
return iter(operands[2])
}
// if the path is empty, then we skip selecting nested keys and return the whole object
if path.Len() == 0 {
return iter(operands[0])
}
// build an ast.Ref from the array and see if it matches within the object
pathRef := ref.ArrayPath(path)
value, err := object.Find(pathRef)
if err != nil {
return iter(operands[2])
}
return iter(ast.NewTerm(value))
return iter(cmp.Or(curr, defaultValue))
}
func builtinObjectKeys(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
@@ -207,39 +214,49 @@ func mergeWithOverwrite(objA, objB ast.Object) ast.Object {
// Modifies obj with any new keys from other, and recursively
// merges any keys where the values are both objects.
func mergewithOverwriteInPlace(obj, other ast.Object, frozenKeys map[*ast.Term]struct{}) {
other.Foreach(func(k, v *ast.Term) {
v2 := obj.Get(k)
// The key didn't exist in other, keep the original value.
if v2 == nil {
nestedObj, ok := v.Value.(ast.Object)
if !ok {
// v is not an object
obj.Insert(k, v)
} else {
// Copy the nested object so the original object would not be modified
nestedObjCopy := nestedObj.Copy()
obj.Insert(k, ast.NewTerm(nestedObjCopy))
}
func mergewithOverwriteInPlace(dst, src ast.Object, frozenKeys map[*ast.Term]struct{}) {
if src.Len() == 0 {
return
}
return
}
// The key exists in both. Merge or reject change.
updateValueObj, ok2 := v.Value.(ast.Object)
originalValueObj, ok1 := v2.Value.(ast.Object)
// Both are objects? Merge recursively.
if ok1 && ok2 {
// Check to make sure that this key isn't frozen before merging.
if _, ok := frozenKeys[v2]; !ok {
mergewithOverwriteInPlace(originalValueObj, updateValueObj, frozenKeys)
}
src.Foreach(func(k, v *ast.Term) {
if v2 := dst.Get(k); v2 == nil {
// key not in dst, insert from src
dst.Insert(k, copyIfObject(v))
} else {
// Else, original value wins. Freeze the key.
frozenKeys[v2] = struct{}{}
// key in both, merge or reject change
srcObj, ok2 := v.Value.(ast.Object)
dstObj, ok1 := v2.Value.(ast.Object)
// both are objects? Merge recursively.
if ok1 && ok2 {
// Check to make sure that this key isn't frozen before merging.
if _, ok := frozenKeys[v2]; !ok {
mergewithOverwriteInPlace(dstObj, srcObj, frozenKeys)
}
} else {
// Else, original value wins. Freeze the key.
frozenKeys[v2] = struct{}{}
}
}
})
}
// copyIfObject returns term in which objects are copied recursively
// other values are returned as-is. This is much cheaper than .Copy()
// and sufficient for the use case of merging, as sets and arrays are
// overwritten rather than merged.
func copyIfObject(term *ast.Term) *ast.Term {
switch val := term.Value.(type) {
case ast.Object:
cpy, _ := val.Map(func(k, v *ast.Term) (*ast.Term, *ast.Term, error) {
return k, copyIfObject(v), nil
})
return ast.NewTerm(cpy)
default:
return term
}
}
func init() {
RegisterBuiltinFunc(ast.ObjectUnion.Name, builtinObjectUnion)
RegisterBuiltinFunc(ast.ObjectUnionN.Name, builtinObjectUnionN)

View File

@@ -135,6 +135,11 @@ func builtinFormatInt(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Ter
case ast.Number("8"):
format = "%o"
case ast.Number("10"):
// Fast path: for numbers whose decimal string is already interned (e.g.
// "0""100"), we can skip strconv.ParseInt entirely.
if term := ast.InternedStringTermFromNumber(input); term != nil {
return iter(term)
}
if i, ok := input.Int(); ok {
return iter(ast.InternedIntegerString(i))
}

View File

@@ -10,7 +10,7 @@ import (
"runtime/debug"
)
var Version = "1.17.1"
var Version = "1.18.1"
// GoVersion is the version of Go this was built with
var GoVersion = runtime.Version()

View File

@@ -295,6 +295,7 @@ func (w *Walker) walkSelection(parentDef *ast.Definition, it ast.Selection) {
if def != nil && !w.validatedFragmentSpreads[def.Name] {
// prevent infinite recursion
w.validatedFragmentSpreads[def.Name] = true
w.walkDirectives(nextParentDef, def.Directives, ast.LocationFragmentDefinition)
w.walkSelectionSet(nextParentDef, def.SelectionSet)
}

View File

@@ -145,6 +145,7 @@ func ValidateSchemaDocument(sd *SchemaDocument) (*Schema, error) {
sd.Schema[0].Directives,
LocationSchema,
nil,
true,
); err != nil {
return nil, err
}
@@ -171,7 +172,13 @@ func ValidateSchemaDocument(sd *SchemaDocument) (*Schema, error) {
schema.Subscription = def
}
}
if err := validateDirectives(&schema, ext.Directives, LocationSchema, nil); err != nil {
if err := validateDirectives(
&schema,
ext.Directives,
LocationSchema,
nil,
true,
); err != nil {
return nil, err
}
schema.SchemaDirectives = append(schema.SchemaDirectives, ext.Directives...)
@@ -277,7 +284,13 @@ func validateDefinition(schema *Schema, def *Definition) *gqlerror.Error {
if def.Kind == InputObject {
wantDirLocation = LocationInputFieldDefinition
}
if err := validateDirectives(schema, field.Directives, wantDirLocation, nil); err != nil {
if err := validateDirectives(
schema,
field.Directives,
wantDirLocation,
nil,
true,
); err != nil {
return err
}
}
@@ -353,6 +366,7 @@ func validateDefinition(schema *Schema, def *Definition) *gqlerror.Error {
value.Directives,
LocationEnumValue,
nil,
true,
); err != nil {
return err
}
@@ -394,6 +408,19 @@ func validateDefinition(schema *Schema, def *Definition) *gqlerror.Error {
}
}
for idx, value1 := range def.EnumValues {
for _, value2 := range def.EnumValues[idx+1:] {
if value1.Name == value2.Name {
return gqlerror.ErrorPosf(
value2.Position,
"Enum value %s.%s can only be defined once.",
def.Name,
value2.Name,
)
}
}
}
if !def.BuiltIn {
// GraphQL spec has reserved type names a lot!
err := validateName(def.Position, def.Name)
@@ -402,7 +429,9 @@ func validateDefinition(schema *Schema, def *Definition) *gqlerror.Error {
}
}
return validateDirectives(schema, def.Directives, DirectiveLocation(def.Kind), nil)
// def.Directives is merged across the base definition and all extensions,
// which are distinct locations, so uniqueness is not enforced here.
return validateDirectives(schema, def.Directives, DirectiveLocation(def.Kind), nil, false)
}
func validateTypeRef(schema *Schema, typ *Type) *gqlerror.Error {
@@ -440,6 +469,7 @@ func validateArgs(
arg.Directives,
LocationArgumentDefinition,
currentDirective,
true,
); err != nil {
return err
}
@@ -452,7 +482,15 @@ func validateDirectives(
dirs DirectiveList,
location DirectiveLocation,
currentDirective *DirectiveDefinition,
// singleLocation is true when dirs come from a single authored location
// (one field, enum value, argument, or schema/extension node). A type's
// own directives are merged across its base definition and every extension
// (see the def.Directives append in LoadSchema), which the spec treats as
// distinct locations, so the non-repeatable check is skipped for that
// merged list to avoid rejecting a directive used once per location.
singleLocation bool,
) *gqlerror.Error {
seen := make(map[string]bool, len(dirs))
for _, dir := range dirs {
if err := validateName(dir.Position, dir.Name); err != nil {
// now, GraphQL spec doesn't have reserved directive name
@@ -469,6 +507,16 @@ func validateDirectives(
if dirDefinition == nil {
return gqlerror.ErrorPosf(dir.Position, "Undefined directive %s.", dir.Name)
}
if singleLocation {
if seen[dir.Name] && !dirDefinition.IsRepeatable {
return gqlerror.ErrorPosf(
dir.Position,
"The directive %s can only be used once at this location.",
dir.Name,
)
}
seen[dir.Name] = true
}
validKind := slices.Contains(dirDefinition.Locations, location)
if !validKind {
return gqlerror.ErrorPosf(

View File

@@ -445,6 +445,26 @@ enums:
error:
message: 'ENUM InvalidEnum2: must define one or more unique enum values.'
locations: [{line: 6, column: 6}]
- name: cannot be duplicated enum value at same definition
input: |
enum A {
FOO
FOO
}
error:
message: "Enum value A.FOO can only be defined once."
locations: [{line: 3, column: 3}]
- name: cannot be duplicated enum value across extension
input: |
enum A {
FOO
}
extend enum A {
FOO
}
error:
message: "Enum value A.FOO can only be defined once."
locations: [{line: 5, column: 3}]
- name: check reserved names on type name
input: |
enum __FooBar {
@@ -570,6 +590,45 @@ directives:
error:
message: "Directive A cannot refer to itself."
locations: [{line: 1, column: 25}]
- name: non-repeatable directive cannot be used more than once per location
input: |
directive @A on FIELD_DEFINITION
type User {
name: String @A @A
}
error:
message: "The directive A can only be used once at this location."
locations: [{line: 3, column: 20}]
- name: non-repeatable directive cannot be used more than once on an enum value
input: |
directive @A on ENUM_VALUE
enum E {
FOO @A @A
}
error:
message: "The directive A can only be used once at this location."
locations: [{line: 3, column: 11}]
- name: repeatable directive can be used more than once per location
input: |
directive @A repeatable on FIELD_DEFINITION
type User {
name: String @A @A
}
- name: non-repeatable directive can be used on distinct locations
input: |
directive @A on FIELD_DEFINITION
type User {
a: String @A
b: String @A
}
- name: non-repeatable directive can be used on a type and its extension
input: |
directive @A on OBJECT
type User @A {
name: String
}
extend type User @A
- name: check reserved names on type name
input: |
directive @__A on FIELD_DEFINITION

View File

@@ -35,10 +35,6 @@ type middleware struct {
semconv semconv.HTTPServer
}
func defaultHandlerFormatter(operation string, _ *http.Request) string {
return operation
}
// NewHandler wraps the passed handler in a span named after the operation and
// enriches it with metrics.
func NewHandler(handler http.Handler, operation string, opts ...Option) http.Handler {
@@ -55,12 +51,17 @@ func NewMiddleware(operation string, opts ...Option) func(http.Handler) http.Han
defaultOpts := []Option{
WithSpanOptions(trace.WithSpanKind(trace.SpanKindServer)),
WithSpanNameFormatter(defaultHandlerFormatter),
}
c := newConfig(append(defaultOpts, opts...)...)
h.configure(c)
if h.spanNameFormatter == nil {
h.spanNameFormatter = func(_ string, r *http.Request) string {
return h.semconv.SpanName(r)
}
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h.serveHTTP(w, r, next)
@@ -138,7 +139,13 @@ func (h *middleware) serveHTTP(w http.ResponseWriter, r *http.Request, next http
// ReadCloser fulfills a certain interface and it is indeed nil or NoBody.
bw := request.NewBodyWrapper(r.Body, readRecordFunc)
if r.Body != nil && r.Body != http.NoBody {
origReq := r
prevBody := r.Body
r.Body = bw
// Restore the original body after the request is processed to avoid issues
// with extra wrapper since `http/server.go` later checks type of `r.Body`.
defer func() { origReq.Body = prevBody }()
}
writeRecordFunc := func(int64) {}

View File

@@ -12,6 +12,7 @@ import (
"context"
"fmt"
"net/http"
"reflect"
"slices"
"strconv"
"strings"
@@ -19,8 +20,8 @@ import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/semconv/v1.40.0"
"go.opentelemetry.io/otel/semconv/v1.40.0/httpconv"
"go.opentelemetry.io/otel/semconv/v1.41.0"
"go.opentelemetry.io/otel/semconv/v1.41.0/httpconv"
)
type HTTPClient struct {
@@ -166,7 +167,7 @@ func (n HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue
func (n HTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) {
if method == "" {
return semconv.HTTPRequestMethodGet, attribute.KeyValue{}
return semconv.HTTPRequestMethodOther, attribute.KeyValue{}
}
if attr, ok := methodLookup[method]; ok {
return attr, attribute.KeyValue{}
@@ -176,7 +177,7 @@ func (n HTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValu
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
return attr, orig
}
return semconv.HTTPRequestMethodGet, orig
return semconv.HTTPRequestMethodOther, orig
}
func (n HTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
@@ -249,6 +250,9 @@ func (o MetricOpts) AddOptions() metric.AddOption {
func (n HTTPClient) MetricOptions(ma MetricAttributes) MetricOpts {
attributes := n.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes)
if ma.StatusCode == 0 && ma.Err != nil {
attributes = append(attributes, n.ErrorType(ma.Err))
}
set := metric.WithAttributeSet(attribute.NewSet(attributes...))
return MetricOpts{
@@ -257,6 +261,39 @@ func (n HTTPClient) MetricOptions(ma MetricAttributes) MetricOpts {
}
}
// ErrorType returns an error.type attribute for the given error. The otelhttp
// Transport calls the underlying RoundTripper directly, so transport failures
// arrive as *net.OpError (connection refused, timeout, etc.) rather than
// *url.Error (which http.Client.Do adds above the Transport layer). This
// function intentionally does not unwrap further: reporting a single concrete
// type per failure keeps attribute cardinality bounded, as required by the
// OTel spec (error.type SHOULD have low cardinality). Callers that need
// finer-grained error distinctions should inspect the error themselves.
func (n HTTPClient) ErrorType(err error) attribute.KeyValue {
t := reflect.TypeOf(err)
if t == nil {
return semconv.ErrorTypeOther
}
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
var value string
if t.PkgPath() == "" || t.Name() == "" {
// t.PkgPath() == "" covers builtin and unnamed types.
// t.Name() == "" covers anonymous struct types that implement error,
// which are uncommon but possible. Fall back to t.String() for both.
value = t.String()
} else {
value = fmt.Sprintf("%s.%s", t.PkgPath(), t.Name())
}
if value == "" {
return semconv.ErrorTypeOther
}
return semconv.ErrorTypeKey.String(value)
}
func (n HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts MetricOpts) {
recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption)
defer func() {

View File

@@ -20,8 +20,8 @@ import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/semconv/v1.40.0"
"go.opentelemetry.io/otel/semconv/v1.40.0/httpconv"
"go.opentelemetry.io/otel/semconv/v1.41.0"
"go.opentelemetry.io/otel/semconv/v1.41.0/httpconv"
)
type RequestTraceAttrsOpts struct {
@@ -243,6 +243,7 @@ type MetricAttributes struct {
StatusCode int
Route string
AdditionalAttributes []attribute.KeyValue
Err error
}
type MetricData struct {
@@ -250,13 +251,11 @@ type MetricData struct {
RequestDuration time.Duration
}
var (
metricRecordOptionPool = &sync.Pool{
New: func() any {
return &[]metric.RecordOption{}
},
}
)
var metricRecordOptionPool = &sync.Pool{
New: func() any {
return &[]metric.RecordOption{}
},
}
func (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) {
attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.Route, md.AdditionalAttributes)
@@ -270,9 +269,27 @@ func (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) {
metricRecordOptionPool.Put(recordOpts)
}
// SpanName returns the span name for an HTTP request following the
// OpenTelemetry HTTP semantic conventions.
// It returns "{method} {route}" when the request has a pattern,
// or just "{method}" when no route is available.
// Non-standard HTTP methods are replaced by "HTTP".
func (n HTTPServer) SpanName(r *http.Request) string {
method := strings.ToUpper(r.Method)
if _, ok := methodLookup[method]; !ok {
method = "HTTP"
}
route := httpRoute(r.Pattern)
if route != "" {
return method + " " + route
}
return method
}
func (n HTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) {
if method == "" {
return semconv.HTTPRequestMethodGet, attribute.KeyValue{}
return semconv.HTTPRequestMethodOther, attribute.KeyValue{}
}
if attr, ok := methodLookup[method]; ok {
return attr, attribute.KeyValue{}
@@ -282,7 +299,7 @@ func (n HTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValu
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
return attr, orig
}
return semconv.HTTPRequestMethodGet, orig
return semconv.HTTPRequestMethodOther, orig
}
func (n HTTPServer) scheme(https bool) attribute.KeyValue { //nolint:revive // ignore linter

View File

@@ -15,7 +15,7 @@ import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
semconvNew "go.opentelemetry.io/otel/semconv/v1.40.0"
semconvNew "go.opentelemetry.io/otel/semconv/v1.41.0"
)
// SplitHostPort splits a network address hostport of the form "host",

View File

@@ -15,7 +15,7 @@ import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/propagation"
otelsemconv "go.opentelemetry.io/otel/semconv/v1.40.0"
otelsemconv "go.opentelemetry.io/otel/semconv/v1.41.0"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request"
@@ -161,6 +161,7 @@ func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
t.semconv.MetricOptions(semconv.MetricAttributes{
Req: r,
StatusCode: statusCode,
Err: err,
AdditionalAttributes: append(labeler.Get(), t.metricAttributesFromRequest(r)...),
}),
)

View File

@@ -4,4 +4,4 @@
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
// Version is the current release version of the otelhttp instrumentation.
const Version = "0.68.0"
const Version = "0.69.0"

View File

@@ -1,27 +0,0 @@
<!-- Generated. DO NOT MODIFY. -->
# Migration from v1.39.0 to v1.40.0
The `go.opentelemetry.io/otel/semconv/v1.40.0` package should be a drop-in replacement for `go.opentelemetry.io/otel/semconv/v1.39.0` with the following exceptions.
## Removed
The following declarations have been removed.
Refer to the [OpenTelemetry Semantic Conventions documentation] for deprecation instructions.
If the type is not listed in the documentation as deprecated, it has been removed in this version due to lack of applicability or use.
If you use any of these non-deprecated declarations in your Go application, please [open an issue] describing your use-case.
- `ErrorMessage`
- `ErrorMessageKey`
- `RPCMessageCompressedSize`
- `RPCMessageCompressedSizeKey`
- `RPCMessageID`
- `RPCMessageIDKey`
- `RPCMessageTypeKey`
- `RPCMessageTypeReceived`
- `RPCMessageTypeSent`
- `RPCMessageUncompressedSize`
- `RPCMessageUncompressedSizeKey`
[OpenTelemetry Semantic Conventions documentation]: https://github.com/open-telemetry/semantic-conventions
[open an issue]: https://github.com/open-telemetry/opentelemetry-go/issues/new?template=Blank+issue

View File

@@ -1,3 +0,0 @@
# Semconv v1.40.0
[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/semconv/v1.40.0)](https://pkg.go.dev/go.opentelemetry.io/otel/semconv/v1.40.0)

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +0,0 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package semconv implements OpenTelemetry semantic conventions.
//
// OpenTelemetry semantic conventions are agreed standardized naming
// patterns for OpenTelemetry things. This package represents the v1.40.0
// version of the OpenTelemetry semantic conventions.
package semconv // import "go.opentelemetry.io/otel/semconv/v1.40.0"

View File

@@ -1,81 +0,0 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package semconv // import "go.opentelemetry.io/otel/semconv/v1.40.0"
import (
"errors"
"fmt"
"reflect"
"go.opentelemetry.io/otel/attribute"
)
// ErrorType returns an [attribute.KeyValue] identifying the error type of err.
//
// If err is nil, the returned attribute has the default value
// [ErrorTypeOther].
//
// If err or one of the errors in its chain has the method
//
// ErrorType() string
//
// the returned attribute has that method's return value. If multiple errors in
// the chain implement this method, the value from the first match found by
// [errors.As] is used. Otherwise, the returned attribute has a value derived
// from the concrete type of err after unwrapping any wrappers created with
// [fmt.Errorf].
//
// The key of the returned attribute is [ErrorTypeKey].
func ErrorType(err error) attribute.KeyValue {
if err == nil {
return ErrorTypeOther
}
return ErrorTypeKey.String(errorType(err))
}
func errorType(err error) string {
var s string
if et, ok := err.(interface{ ErrorType() string }); ok {
// Fast path: check the top-level error first.
s = et.ErrorType()
} else {
// Fallback: search the error chain for an ErrorType method.
var et interface{ ErrorType() string }
if errors.As(err, &et) {
// Prioritize the ErrorType method if available.
s = et.ErrorType()
}
}
if s == "" {
// Fallback to reflection if the ErrorType method is not supported or
// returns an empty value.
t := reflect.TypeOf(unwrapFmtWrapped(err))
pkg, name := t.PkgPath(), t.Name()
if pkg != "" && name != "" {
s = pkg + "." + name
} else {
// The type has no package path or name (predeclared, not-defined,
// or alias for a not-defined type).
//
// This is not guaranteed to be unique, but is a best effort.
s = t.String()
}
}
return s
}
var fmtWrapErrorType = reflect.TypeOf(fmt.Errorf("wrapped: %w", errors.New("err")))
func unwrapFmtWrapped(err error) error {
for reflect.TypeOf(err) == fmtWrapErrorType {
u := errors.Unwrap(err)
if u == nil {
return err // Should never happen, but avoid returning nil if unwrapping fails.
}
err = u
}
return err
}

View File

@@ -1,9 +0,0 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package semconv // import "go.opentelemetry.io/otel/semconv/v1.40.0"
const (
// ExceptionEventName is the name of the Span event representing an exception.
ExceptionEventName = "exception"
)

View File

@@ -1,9 +0,0 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package semconv // import "go.opentelemetry.io/otel/semconv/v1.40.0"
// SchemaURL is the schema URL that matches the version of the semantic conventions
// that this package defines. Semconv packages starting from v1.4.0 must declare
// non-empty schema URL in the form https://opentelemetry.io/schemas/<version>
const SchemaURL = "https://opentelemetry.io/schemas/1.40.0"

View File

@@ -26,9 +26,11 @@ var (
// with.
type ErrorTypeAttr string
// ErrorTypeOther is a fallback error value to be used when the instrumentation
// doesn't define a custom value.
var ErrorTypeOther ErrorTypeAttr = "_OTHER"
var (
// ErrorTypeOther is a fallback error value to be used when the instrumentation
// doesn't define a custom value.
ErrorTypeOther ErrorTypeAttr = "_OTHER"
)
// ConnectionStateAttr is an attribute conforming to the http.connection.state
// semantic conventions. It represents the state of the HTTP connection in the
@@ -170,6 +172,7 @@ func (m ClientActiveRequests) Add(
o := addOptPool.Get().(*[]metric.AddOption)
defer func() {
clear(*o)
*o = (*o)[:0]
addOptPool.Put(o)
}()
@@ -200,6 +203,7 @@ func (m ClientActiveRequests) AddSet(ctx context.Context, incr int64, set attrib
o := addOptPool.Get().(*[]metric.AddOption)
defer func() {
clear(*o)
*o = (*o)[:0]
addOptPool.Put(o)
}()
@@ -232,6 +236,102 @@ func (ClientActiveRequests) AttrURLScheme(val string) attribute.KeyValue {
return attribute.String("url.scheme", val)
}
// ClientActiveRequestsObservable is an instrument used to record metric values
// conforming to the "http.client.active_requests" semantic conventions. It
// represents the number of active HTTP requests.
type ClientActiveRequestsObservable struct {
metric.Int64ObservableUpDownCounter
}
var newClientActiveRequestsObservableOpts = []metric.Int64ObservableUpDownCounterOption{
metric.WithDescription("Number of active HTTP requests."),
metric.WithUnit("{request}"),
}
// NewClientActiveRequestsObservable returns a new ClientActiveRequestsObservable
// instrument.
func NewClientActiveRequestsObservable(
m metric.Meter,
opt ...metric.Int64ObservableUpDownCounterOption,
) (ClientActiveRequestsObservable, error) {
// Check if the meter is nil.
if m == nil {
return ClientActiveRequestsObservable{noop.Int64ObservableUpDownCounter{}}, nil
}
if len(opt) == 0 {
opt = newClientActiveRequestsObservableOpts
} else {
opt = append(opt, newClientActiveRequestsObservableOpts...)
}
i, err := m.Int64ObservableUpDownCounter(
"http.client.active_requests",
opt...,
)
if err != nil {
return ClientActiveRequestsObservable{noop.Int64ObservableUpDownCounter{}}, err
}
return ClientActiveRequestsObservable{i}, nil
}
// Inst returns the underlying metric instrument.
func (m ClientActiveRequestsObservable) Inst() metric.Int64ObservableUpDownCounter {
return m.Int64ObservableUpDownCounter
}
// Name returns the semantic convention name of the instrument.
func (ClientActiveRequestsObservable) Name() string {
return "http.client.active_requests"
}
// Unit returns the semantic convention unit of the instrument
func (ClientActiveRequestsObservable) Unit() string {
return "{request}"
}
// Description returns the semantic convention description of the instrument
func (ClientActiveRequestsObservable) Description() string {
return "Number of active HTTP requests."
}
// AttrServerAddress returns a required attribute for the "server.address"
// semantic convention. It represents the server domain name if available without
// reverse DNS lookup; otherwise, IP address or Unix domain socket name.
func (ClientActiveRequestsObservable) AttrServerAddress(val string) attribute.KeyValue {
return attribute.String("server.address", val)
}
// AttrServerPort returns a required attribute for the "server.port" semantic
// convention. It represents the server port number.
func (ClientActiveRequestsObservable) AttrServerPort(val int) attribute.KeyValue {
return attribute.Int("server.port", val)
}
// AttrURLTemplate returns an optional attribute for the "url.template" semantic
// convention. It represents the low-cardinality template of an
// [absolute path reference].
//
// [absolute path reference]: https://www.rfc-editor.org/rfc/rfc3986#section-4.2
func (ClientActiveRequestsObservable) AttrURLTemplate(val string) attribute.KeyValue {
return attribute.String("url.template", val)
}
// AttrRequestMethod returns an optional attribute for the "http.request.method"
// semantic convention. It represents the HTTP request method.
func (ClientActiveRequestsObservable) AttrRequestMethod(val RequestMethodAttr) attribute.KeyValue {
return attribute.String("http.request.method", string(val))
}
// AttrURLScheme returns an optional attribute for the "url.scheme" semantic
// convention. It represents the [URI scheme] component identifying the used
// protocol.
//
// [URI scheme]: https://www.rfc-editor.org/rfc/rfc3986#section-3.1
func (ClientActiveRequestsObservable) AttrURLScheme(val string) attribute.KeyValue {
return attribute.String("url.scheme", val)
}
// ClientConnectionDuration is an instrument used to record metric values
// conforming to the "http.client.connection.duration" semantic conventions. It
// represents the duration of the successfully established outbound HTTP
@@ -319,6 +419,7 @@ func (m ClientConnectionDuration) Record(
o := recOptPool.Get().(*[]metric.RecordOption)
defer func() {
clear(*o)
*o = (*o)[:0]
recOptPool.Put(o)
}()
@@ -349,6 +450,7 @@ func (m ClientConnectionDuration) RecordSet(ctx context.Context, val float64, se
o := recOptPool.Get().(*[]metric.RecordOption)
defer func() {
clear(*o)
*o = (*o)[:0]
recOptPool.Put(o)
}()
@@ -472,6 +574,7 @@ func (m ClientOpenConnections) Add(
o := addOptPool.Get().(*[]metric.AddOption)
defer func() {
clear(*o)
*o = (*o)[:0]
addOptPool.Put(o)
}()
@@ -503,6 +606,7 @@ func (m ClientOpenConnections) AddSet(ctx context.Context, incr int64, set attri
o := addOptPool.Get().(*[]metric.AddOption)
defer func() {
clear(*o)
*o = (*o)[:0]
addOptPool.Put(o)
}()
@@ -534,6 +638,109 @@ func (ClientOpenConnections) AttrURLScheme(val string) attribute.KeyValue {
return attribute.String("url.scheme", val)
}
// ClientOpenConnectionsObservable is an instrument used to record metric values
// conforming to the "http.client.open_connections" semantic conventions. It
// represents the number of outbound HTTP connections that are currently active
// or idle on the client.
type ClientOpenConnectionsObservable struct {
metric.Int64ObservableUpDownCounter
}
var newClientOpenConnectionsObservableOpts = []metric.Int64ObservableUpDownCounterOption{
metric.WithDescription("Number of outbound HTTP connections that are currently active or idle on the client."),
metric.WithUnit("{connection}"),
}
// NewClientOpenConnectionsObservable returns a new
// ClientOpenConnectionsObservable instrument.
func NewClientOpenConnectionsObservable(
m metric.Meter,
opt ...metric.Int64ObservableUpDownCounterOption,
) (ClientOpenConnectionsObservable, error) {
// Check if the meter is nil.
if m == nil {
return ClientOpenConnectionsObservable{noop.Int64ObservableUpDownCounter{}}, nil
}
if len(opt) == 0 {
opt = newClientOpenConnectionsObservableOpts
} else {
opt = append(opt, newClientOpenConnectionsObservableOpts...)
}
i, err := m.Int64ObservableUpDownCounter(
"http.client.open_connections",
opt...,
)
if err != nil {
return ClientOpenConnectionsObservable{noop.Int64ObservableUpDownCounter{}}, err
}
return ClientOpenConnectionsObservable{i}, nil
}
// Inst returns the underlying metric instrument.
func (m ClientOpenConnectionsObservable) Inst() metric.Int64ObservableUpDownCounter {
return m.Int64ObservableUpDownCounter
}
// Name returns the semantic convention name of the instrument.
func (ClientOpenConnectionsObservable) Name() string {
return "http.client.open_connections"
}
// Unit returns the semantic convention unit of the instrument
func (ClientOpenConnectionsObservable) Unit() string {
return "{connection}"
}
// Description returns the semantic convention description of the instrument
func (ClientOpenConnectionsObservable) Description() string {
return "Number of outbound HTTP connections that are currently active or idle on the client."
}
// AttrConnectionState returns a required attribute for the
// "http.connection.state" semantic convention. It represents the state of the
// HTTP connection in the HTTP connection pool.
func (ClientOpenConnectionsObservable) AttrConnectionState(val ConnectionStateAttr) attribute.KeyValue {
return attribute.String("http.connection.state", string(val))
}
// AttrServerAddress returns a required attribute for the "server.address"
// semantic convention. It represents the server domain name if available without
// reverse DNS lookup; otherwise, IP address or Unix domain socket name.
func (ClientOpenConnectionsObservable) AttrServerAddress(val string) attribute.KeyValue {
return attribute.String("server.address", val)
}
// AttrServerPort returns a required attribute for the "server.port" semantic
// convention. It represents the server port number.
func (ClientOpenConnectionsObservable) AttrServerPort(val int) attribute.KeyValue {
return attribute.Int("server.port", val)
}
// AttrNetworkPeerAddress returns an optional attribute for the
// "network.peer.address" semantic convention. It represents the peer address of
// the network connection - IP address or Unix domain socket name.
func (ClientOpenConnectionsObservable) AttrNetworkPeerAddress(val string) attribute.KeyValue {
return attribute.String("network.peer.address", val)
}
// AttrNetworkProtocolVersion returns an optional attribute for the
// "network.protocol.version" semantic convention. It represents the actual
// version of the protocol used for network communication.
func (ClientOpenConnectionsObservable) AttrNetworkProtocolVersion(val string) attribute.KeyValue {
return attribute.String("network.protocol.version", val)
}
// AttrURLScheme returns an optional attribute for the "url.scheme" semantic
// convention. It represents the [URI scheme] component identifying the used
// protocol.
//
// [URI scheme]: https://www.rfc-editor.org/rfc/rfc3986#section-3.1
func (ClientOpenConnectionsObservable) AttrURLScheme(val string) attribute.KeyValue {
return attribute.String("url.scheme", val)
}
// ClientRequestBodySize is an instrument used to record metric values conforming
// to the "http.client.request.body.size" semantic conventions. It represents the
// size of HTTP client request bodies.
@@ -631,6 +838,7 @@ func (m ClientRequestBodySize) Record(
o := recOptPool.Get().(*[]metric.RecordOption)
defer func() {
clear(*o)
*o = (*o)[:0]
recOptPool.Put(o)
}()
@@ -669,6 +877,7 @@ func (m ClientRequestBodySize) RecordSet(ctx context.Context, val int64, set att
o := recOptPool.Get().(*[]metric.RecordOption)
defer func() {
clear(*o)
*o = (*o)[:0]
recOptPool.Put(o)
}()
@@ -737,6 +946,7 @@ type ClientRequestDuration struct {
var newClientRequestDurationOpts = []metric.Float64HistogramOption{
metric.WithDescription("Duration of HTTP client requests."),
metric.WithUnit("s"),
metric.WithExplicitBucketBoundaries([]float64{0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10}...),
}
// NewClientRequestDuration returns a new ClientRequestDuration instrument.
@@ -817,6 +1027,7 @@ func (m ClientRequestDuration) Record(
o := recOptPool.Get().(*[]metric.RecordOption)
defer func() {
clear(*o)
*o = (*o)[:0]
recOptPool.Put(o)
}()
@@ -848,6 +1059,7 @@ func (m ClientRequestDuration) RecordSet(ctx context.Context, val float64, set a
o := recOptPool.Get().(*[]metric.RecordOption)
defer func() {
clear(*o)
*o = (*o)[:0]
recOptPool.Put(o)
}()
@@ -1003,6 +1215,7 @@ func (m ClientResponseBodySize) Record(
o := recOptPool.Get().(*[]metric.RecordOption)
defer func() {
clear(*o)
*o = (*o)[:0]
recOptPool.Put(o)
}()
@@ -1041,6 +1254,7 @@ func (m ClientResponseBodySize) RecordSet(ctx context.Context, val int64, set at
o := recOptPool.Get().(*[]metric.RecordOption)
defer func() {
clear(*o)
*o = (*o)[:0]
recOptPool.Put(o)
}()
@@ -1186,6 +1400,7 @@ func (m ServerActiveRequests) Add(
o := addOptPool.Get().(*[]metric.AddOption)
defer func() {
clear(*o)
*o = (*o)[:0]
addOptPool.Put(o)
}()
@@ -1216,6 +1431,7 @@ func (m ServerActiveRequests) AddSet(ctx context.Context, incr int64, set attrib
o := addOptPool.Get().(*[]metric.AddOption)
defer func() {
clear(*o)
*o = (*o)[:0]
addOptPool.Put(o)
}()
@@ -1238,6 +1454,94 @@ func (ServerActiveRequests) AttrServerPort(val int) attribute.KeyValue {
return attribute.Int("server.port", val)
}
// ServerActiveRequestsObservable is an instrument used to record metric values
// conforming to the "http.server.active_requests" semantic conventions. It
// represents the number of active HTTP server requests.
type ServerActiveRequestsObservable struct {
metric.Int64ObservableUpDownCounter
}
var newServerActiveRequestsObservableOpts = []metric.Int64ObservableUpDownCounterOption{
metric.WithDescription("Number of active HTTP server requests."),
metric.WithUnit("{request}"),
}
// NewServerActiveRequestsObservable returns a new ServerActiveRequestsObservable
// instrument.
func NewServerActiveRequestsObservable(
m metric.Meter,
opt ...metric.Int64ObservableUpDownCounterOption,
) (ServerActiveRequestsObservable, error) {
// Check if the meter is nil.
if m == nil {
return ServerActiveRequestsObservable{noop.Int64ObservableUpDownCounter{}}, nil
}
if len(opt) == 0 {
opt = newServerActiveRequestsObservableOpts
} else {
opt = append(opt, newServerActiveRequestsObservableOpts...)
}
i, err := m.Int64ObservableUpDownCounter(
"http.server.active_requests",
opt...,
)
if err != nil {
return ServerActiveRequestsObservable{noop.Int64ObservableUpDownCounter{}}, err
}
return ServerActiveRequestsObservable{i}, nil
}
// Inst returns the underlying metric instrument.
func (m ServerActiveRequestsObservable) Inst() metric.Int64ObservableUpDownCounter {
return m.Int64ObservableUpDownCounter
}
// Name returns the semantic convention name of the instrument.
func (ServerActiveRequestsObservable) Name() string {
return "http.server.active_requests"
}
// Unit returns the semantic convention unit of the instrument
func (ServerActiveRequestsObservable) Unit() string {
return "{request}"
}
// Description returns the semantic convention description of the instrument
func (ServerActiveRequestsObservable) Description() string {
return "Number of active HTTP server requests."
}
// AttrRequestMethod returns a required attribute for the "http.request.method"
// semantic convention. It represents the HTTP request method.
func (ServerActiveRequestsObservable) AttrRequestMethod(val RequestMethodAttr) attribute.KeyValue {
return attribute.String("http.request.method", string(val))
}
// AttrURLScheme returns a required attribute for the "url.scheme" semantic
// convention. It represents the [URI scheme] component identifying the used
// protocol.
//
// [URI scheme]: https://www.rfc-editor.org/rfc/rfc3986#section-3.1
func (ServerActiveRequestsObservable) AttrURLScheme(val string) attribute.KeyValue {
return attribute.String("url.scheme", val)
}
// AttrServerAddress returns an optional attribute for the "server.address"
// semantic convention. It represents the name of the local HTTP server that
// received the request.
func (ServerActiveRequestsObservable) AttrServerAddress(val string) attribute.KeyValue {
return attribute.String("server.address", val)
}
// AttrServerPort returns an optional attribute for the "server.port" semantic
// convention. It represents the port of the local HTTP server that received the
// request.
func (ServerActiveRequestsObservable) AttrServerPort(val int) attribute.KeyValue {
return attribute.Int("server.port", val)
}
// ServerRequestBodySize is an instrument used to record metric values conforming
// to the "http.server.request.body.size" semantic conventions. It represents the
// size of HTTP server request bodies.
@@ -1304,12 +1608,13 @@ func (ServerRequestBodySize) Description() string {
//
// All additional attrs passed are included in the recorded value.
//
// [URI scheme]: https://www.rfc-editor.org/rfc/rfc3986#section-3.1
//
// The size of the request payload body in bytes. This is the number of bytes
// transferred excluding headers and is often, but not always, present as the
// [Content-Length] header. For requests using transport encoding, this should be
// the compressed size.
//
// [URI scheme]: https://www.rfc-editor.org/rfc/rfc3986#section-3.1
// [Content-Length]: https://www.rfc-editor.org/rfc/rfc9110.html#field.content-length
func (m ServerRequestBodySize) Record(
ctx context.Context,
@@ -1331,6 +1636,7 @@ func (m ServerRequestBodySize) Record(
o := recOptPool.Get().(*[]metric.RecordOption)
defer func() {
clear(*o)
*o = (*o)[:0]
recOptPool.Put(o)
}()
@@ -1368,6 +1674,7 @@ func (m ServerRequestBodySize) RecordSet(ctx context.Context, val int64, set att
o := recOptPool.Get().(*[]metric.RecordOption)
defer func() {
clear(*o)
*o = (*o)[:0]
recOptPool.Put(o)
}()
@@ -1447,6 +1754,7 @@ type ServerRequestDuration struct {
var newServerRequestDurationOpts = []metric.Float64HistogramOption{
metric.WithDescription("Duration of HTTP server requests."),
metric.WithUnit("s"),
metric.WithExplicitBucketBoundaries([]float64{0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10}...),
}
// NewServerRequestDuration returns a new ServerRequestDuration instrument.
@@ -1524,6 +1832,7 @@ func (m ServerRequestDuration) Record(
o := recOptPool.Get().(*[]metric.RecordOption)
defer func() {
clear(*o)
*o = (*o)[:0]
recOptPool.Put(o)
}()
@@ -1554,6 +1863,7 @@ func (m ServerRequestDuration) RecordSet(ctx context.Context, val float64, set a
o := recOptPool.Get().(*[]metric.RecordOption)
defer func() {
clear(*o)
*o = (*o)[:0]
recOptPool.Put(o)
}()
@@ -1689,12 +1999,13 @@ func (ServerResponseBodySize) Description() string {
//
// All additional attrs passed are included in the recorded value.
//
// [URI scheme]: https://www.rfc-editor.org/rfc/rfc3986#section-3.1
//
// The size of the response payload body in bytes. This is the number of bytes
// transferred excluding headers and is often, but not always, present as the
// [Content-Length] header. For requests using transport encoding, this should be
// the compressed size.
//
// [URI scheme]: https://www.rfc-editor.org/rfc/rfc3986#section-3.1
// [Content-Length]: https://www.rfc-editor.org/rfc/rfc9110.html#field.content-length
func (m ServerResponseBodySize) Record(
ctx context.Context,
@@ -1716,6 +2027,7 @@ func (m ServerResponseBodySize) Record(
o := recOptPool.Get().(*[]metric.RecordOption)
defer func() {
clear(*o)
*o = (*o)[:0]
recOptPool.Put(o)
}()
@@ -1753,6 +2065,7 @@ func (m ServerResponseBodySize) RecordSet(ctx context.Context, val int64, set at
o := recOptPool.Get().(*[]metric.RecordOption)
defer func() {
clear(*o)
*o = (*o)[:0]
recOptPool.Put(o)
}()

View File

@@ -60,7 +60,7 @@ func Register(b Builder) {
if !envconfig.CaseSensitiveBalancerRegistries {
name = strings.ToLower(name)
if name != b.Name() {
logger.Warningf("Balancer registered with name %q. grpc-go will be switching to case sensitive balancer registries soon. After 2 releases, we will enable the env var by default.", b.Name())
logger.Warningf("Balancer registered with name %q. grpc-go has switched to case sensitive balancer registries. GRPC_GO_EXPERIMENTAL_CASE_SENSITIVE_BALANCER_REGISTRIES env variable will be removed in release v1.82.0", b.Name())
}
}
m[name] = b
@@ -85,7 +85,7 @@ func Get(name string) Builder {
if !envconfig.CaseSensitiveBalancerRegistries {
lowerName := strings.ToLower(name)
if lowerName != name {
logger.Warningf("Balancer retrieved for name %q. grpc-go will be switching to case sensitive balancer registries soon. After 2 releases, we will enable the env var by default.", name)
logger.Warningf("Balancer retrieved for name %q. grpc-go has switched to case sensitive balancer registries. GRPC_GO_EXPERIMENTAL_CASE_SENSITIVE_BALANCER_REGISTRIES env variable will be removed in release v1.82.0", name)
}
name = lowerName
}

View File

@@ -35,9 +35,9 @@ import (
"google.golang.org/grpc/balancer"
"google.golang.org/grpc/balancer/pickfirst/internal"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/experimental/balancer/weight"
expstats "google.golang.org/grpc/experimental/stats"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/internal/balancer/weight"
"google.golang.org/grpc/internal/envconfig"
internalgrpclog "google.golang.org/grpc/internal/grpclog"
"google.golang.org/grpc/internal/pretty"

View File

@@ -173,10 +173,8 @@ func newJoinDialOption(opts ...DialOption) DialOption {
// If this option is set to true every connection will release the buffer after
// flushing the data on the wire.
//
// # Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
// Deprecated: shared write buffer is enabled by default. WithSharedWriteBuffer
// will be removed in a future release.
func WithSharedWriteBuffer(val bool) DialOption {
return newFuncDialOption(func(o *dialOptions) {
o.copts.SharedWriteBuffer = val
@@ -229,6 +227,14 @@ func WithInitialConnWindowSize(s int32) DialOption {
// WithStaticStreamWindowSize returns a DialOption which sets the initial
// stream window size to the value provided and disables dynamic flow control.
//
// Note that this also disables dynamic flow control for the connection,
// falling back to a default static connection-level window of 64KB. To
// use a larger connection-level window, you must also use the
// [WithStaticConnWindowSize] DialOption.
//
// Most users should not configure static flow control windows unless
// operating in a memory-constrained environment.
func WithStaticStreamWindowSize(s int32) DialOption {
return newFuncDialOption(func(o *dialOptions) {
o.copts.InitialWindowSize = s
@@ -239,6 +245,14 @@ func WithStaticStreamWindowSize(s int32) DialOption {
// WithStaticConnWindowSize returns a DialOption which sets the initial
// connection window size to the value provided and disables dynamic flow
// control.
//
// Note that this also disables dynamic flow control for individual streams,
// falling back to a default static connection-level window of 64KB. To
// explicitly configure the stream-level window size, you must also use the
// [WithStaticStreamWindowSize] DialOption.
//
// Most users should not configure static flow control windows unless
// operating in a memory-constrained environment.
func WithStaticConnWindowSize(s int32) DialOption {
return newFuncDialOption(func(o *dialOptions) {
o.copts.InitialConnWindowSize = s

View File

@@ -66,6 +66,9 @@ type Compressor interface {
// Decompress reads data from r, decompresses it, and provides the
// uncompressed data via the returned io.Reader. If an error occurs while
// initializing the decompressor, that error is returned instead.
//
// The returned io.Reader may optionally implement io.ReadCloser, and if it
// does, gRPC will call Close() exactly once.
Decompress(r io.Reader) (io.Reader, error)
// Name is the name of the compression codec and is used to set the content
// coding header. The result must be static; the result cannot change

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