build(deps): bump github.com/open-policy-agent/opa from 1.13.2 to 1.14.0
Bumps [github.com/open-policy-agent/opa](https://github.com/open-policy-agent/opa) from 1.13.2 to 1.14.0. - [Release notes](https://github.com/open-policy-agent/opa/releases) - [Changelog](https://github.com/open-policy-agent/opa/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-policy-agent/opa/compare/v1.13.2...v1.14.0) --- updated-dependencies: - dependency-name: github.com/open-policy-agent/opa dependency-version: 1.14.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
committed by
Ralf Haferkamp
parent
accbd90b43
commit
c288b91312
@@ -62,7 +62,7 @@ require (
|
||||
github.com/onsi/ginkgo v1.16.5
|
||||
github.com/onsi/ginkgo/v2 v2.28.1
|
||||
github.com/onsi/gomega v1.39.1
|
||||
github.com/open-policy-agent/opa v1.13.2
|
||||
github.com/open-policy-agent/opa v1.14.1
|
||||
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89
|
||||
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260204102724-10bcda1b3068
|
||||
github.com/opencloud-eu/reva/v2 v2.42.5
|
||||
@@ -108,7 +108,7 @@ require (
|
||||
golang.org/x/image v0.36.0
|
||||
golang.org/x/net v0.51.0
|
||||
golang.org/x/oauth2 v0.35.0
|
||||
golang.org/x/sync v0.19.0
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/term v0.40.0
|
||||
golang.org/x/text v0.34.0
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57
|
||||
@@ -369,7 +369,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.7 // indirect
|
||||
github.com/vektah/gqlparser/v2 v2.5.31 // indirect
|
||||
github.com/vektah/gqlparser/v2 v2.5.32 // 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
|
||||
@@ -392,12 +392,11 @@ require (
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/mod v0.32.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
golang.org/x/tools v0.41.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.1 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||
|
||||
@@ -287,8 +287,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/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.0 h1:tpqWb0NewSrCYqTvywbcXOhQdWcqephkVkbBmaaqHzc=
|
||||
github.com/dgraph-io/badger/v4 v4.9.0/go.mod h1:5/MEx97uzdPUHR4KtkNt8asfI2T4JiEiQlV7kWUo8c0=
|
||||
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/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=
|
||||
@@ -955,8 +955,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.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
|
||||
github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=
|
||||
github.com/open-policy-agent/opa v1.13.2 h1:c72l7DhxP4g8DEUBOdaU9QBKyA24dZxCcIuZNRZ0yP4=
|
||||
github.com/open-policy-agent/opa v1.13.2/go.mod h1:M3Asy9yp1YTusUU5VQuENDe92GLmamIuceqjw+C8PHY=
|
||||
github.com/open-policy-agent/opa v1.14.1 h1:MhurLB9mSbXmojYFCmGbiC1Uagu1+aFAV4XVotDA86M=
|
||||
github.com/open-policy-agent/opa v1.14.1/go.mod h1:B5gykwJ2l0g0wZS4ClCcpfSSEx51n4NHpTsWfuPwqnQ=
|
||||
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=
|
||||
@@ -1192,7 +1192,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc=
|
||||
@@ -1247,8 +1246,8 @@ github.com/valyala/fastjson v1.6.7 h1:ZE4tRy0CIkh+qDc5McjatheGX2czdn8slQjomexVpB
|
||||
github.com/valyala/fastjson v1.6.7/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||
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.31 h1:YhWGA1mfTjID7qJhd1+Vxhpk5HTgydrGU9IgkWBTJ7k=
|
||||
github.com/vektah/gqlparser/v2 v2.5.31/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts=
|
||||
github.com/vektah/gqlparser/v2 v2.5.32 h1:k9QPJd4sEDTL+qB4ncPLflqTJ3MmjB9SrVzJrawpFSc=
|
||||
github.com/vektah/gqlparser/v2 v2.5.32/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts=
|
||||
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=
|
||||
@@ -1320,8 +1319,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0 h1:ao6Oe+wSebTlQ1OEht7
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0/go.mod h1:u3T6vz0gh/NVzgDgiwkgLxpsSF6PaPmo2il0apGJbls=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0 h1:mq/Qcf28TWz719lE3/hMB4KkyDuLJIvgJnFGcd0kEUI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0/go.mod h1:yk5LXEYhsL2htyDNJbEq7fWzNEigeEdV5xBF/Y+kAv0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 h1:wVZXIWjQSeSmMoxF74LzAnpVQOAFDo3pPji9Y4SOFKc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 h1:MzfofMZN8ulNqobCmCAVbqVL5syHw+eB2qPRkCMA/fQ=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0/go.mod h1:E73G9UFtKRXrxhBsHtG00TB5WxX57lpsQzogDkqBTz8=
|
||||
go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ=
|
||||
@@ -1488,8 +1487,8 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1604,8 +1603,8 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -1778,8 +1777,6 @@ gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.1 h1:tVBILHy0R6e4wkYOn3XmiITt/hEVH4TFMYvAX2Ytz6k=
|
||||
gopkg.in/ini.v1 v1.67.1/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.4.4/go.mod h1:GMnKY+ZuoJ+lVLL+78uSTjwTz2jMazq6AfGKQOYhsPk=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
|
||||
-3
@@ -1,6 +1,3 @@
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
package bundle
|
||||
|
||||
import (
|
||||
|
||||
-3
@@ -2,9 +2,6 @@
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
package capabilities
|
||||
|
||||
import (
|
||||
|
||||
+4916
File diff suppressed because it is too large
Load Diff
+4916
File diff suppressed because it is too large
Load Diff
+639
-569
File diff suppressed because one or more lines are too long
BIN
Binary file not shown.
+1
-1
@@ -133,7 +133,7 @@ func (c *Compiler) removeUnusedCode() error {
|
||||
}
|
||||
caller, ok := c.funcs[callerName]
|
||||
if !ok {
|
||||
return fmt.Errorf("caller not found: %s (%s)", cg[i][0], callerName)
|
||||
continue // without a caller, it should get removed anyways (right?)
|
||||
}
|
||||
callee, ok := c.funcs[calleeName]
|
||||
if !ok {
|
||||
|
||||
+3
-2
@@ -1285,6 +1285,7 @@ func (c *Compiler) compileScan(scan *ir.ScanStmt, result *[]instruction.Instruct
|
||||
}
|
||||
|
||||
func (c *Compiler) compileScanBlock(scan *ir.ScanStmt) ([]instruction.Instruction, error) {
|
||||
//nolint:prealloc // instruction list is known and fixed, clearer as slice literal
|
||||
instrs := []instruction.Instruction{
|
||||
// Execute iterator.
|
||||
instruction.GetLocal{Index: c.local(scan.Source)},
|
||||
@@ -1486,7 +1487,7 @@ func (c *Compiler) compileUpsert(local ir.Local, path []int, value ir.Operand, _
|
||||
}
|
||||
|
||||
func (c *Compiler) compileCallDynamicStmt(stmt *ir.CallDynamicStmt, result *[]instruction.Instruction) error {
|
||||
instrs := []instruction.Instruction{}
|
||||
instrs := make([]instruction.Instruction, 0, 3+3*len(stmt.Path)+len(stmt.Args)+10)
|
||||
larray := c.genLocal()
|
||||
lidx := c.genLocal()
|
||||
|
||||
@@ -1559,7 +1560,7 @@ func (c *Compiler) compileCallStmt(stmt *ir.CallStmt, result *[]instruction.Inst
|
||||
|
||||
func (c *Compiler) compileInternalCall(stmt *ir.CallStmt, index uint32, result *[]instruction.Instruction) error {
|
||||
|
||||
instrs := []instruction.Instruction{}
|
||||
instrs := make([]instruction.Instruction, 0, len(stmt.Args)+4)
|
||||
|
||||
// Prepare function args and call.
|
||||
for _, arg := range stmt.Args {
|
||||
|
||||
-176
@@ -1,176 +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 config implements helper functions to parse OPA's configuration.
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/open-policy-agent/opa/internal/strvals"
|
||||
"github.com/open-policy-agent/opa/v1/keys"
|
||||
"github.com/open-policy-agent/opa/v1/logging"
|
||||
"github.com/open-policy-agent/opa/v1/plugins/rest"
|
||||
"github.com/open-policy-agent/opa/v1/tracing"
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// ServiceOptions stores the options passed to ParseServicesConfig
|
||||
type ServiceOptions struct {
|
||||
Raw json.RawMessage
|
||||
AuthPlugin rest.AuthPluginLookupFunc
|
||||
Keys map[string]*keys.Config
|
||||
Logger logging.Logger
|
||||
DistributedTacingOpts tracing.Options
|
||||
}
|
||||
|
||||
// ParseServicesConfig returns a set of named service clients. The service
|
||||
// clients can be specified either as an array or as a map. Some systems (e.g.,
|
||||
// Helm) do not have proper support for configuration values nested under
|
||||
// arrays, so just support both here.
|
||||
func ParseServicesConfig(opts ServiceOptions) (map[string]rest.Client, error) {
|
||||
|
||||
services := map[string]rest.Client{}
|
||||
|
||||
var arr []json.RawMessage
|
||||
var obj map[string]json.RawMessage
|
||||
|
||||
if err := util.Unmarshal(opts.Raw, &arr); err == nil {
|
||||
for _, s := range arr {
|
||||
client, err := rest.New(s, opts.Keys, rest.AuthPluginLookup(opts.AuthPlugin), rest.Logger(opts.Logger), rest.DistributedTracingOpts(opts.DistributedTacingOpts))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
services[client.Service()] = client
|
||||
}
|
||||
} else if util.Unmarshal(opts.Raw, &obj) == nil {
|
||||
for k := range obj {
|
||||
client, err := rest.New(obj[k], opts.Keys, rest.Name(k), rest.AuthPluginLookup(opts.AuthPlugin), rest.Logger(opts.Logger), rest.DistributedTracingOpts(opts.DistributedTacingOpts))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
services[client.Service()] = client
|
||||
}
|
||||
} else {
|
||||
// Return error from array decode as that is the default format.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return services, nil
|
||||
}
|
||||
|
||||
// Load implements configuration file loading. The supplied config file will be
|
||||
// read from disk (if specified) and overrides will be applied. If no config file is
|
||||
// specified, the overrides can still be applied to an empty config.
|
||||
func Load(configFile string, overrides []string, overrideFiles []string) ([]byte, error) {
|
||||
baseConf := map[string]any{}
|
||||
|
||||
// User specified config file
|
||||
if configFile != "" {
|
||||
var bytes []byte
|
||||
var err error
|
||||
bytes, err = os.ReadFile(configFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
processedConf := subEnvVars(string(bytes))
|
||||
|
||||
if err := yaml.Unmarshal([]byte(processedConf), &baseConf); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse %s: %s", configFile, err)
|
||||
}
|
||||
}
|
||||
|
||||
overrideConf := map[string]any{}
|
||||
|
||||
// User specified a config override via --set
|
||||
for _, override := range overrides {
|
||||
processedOverride := subEnvVars(override)
|
||||
if err := strvals.ParseInto(processedOverride, overrideConf); err != nil {
|
||||
return nil, fmt.Errorf("failed parsing --set data: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// User specified a config override value via --set-file
|
||||
for _, override := range overrideFiles {
|
||||
reader := func(rs []rune) (any, error) {
|
||||
bytes, err := os.ReadFile(string(rs))
|
||||
value := strings.TrimSpace(string(bytes))
|
||||
return value, err
|
||||
}
|
||||
if err := strvals.ParseIntoFile(override, overrideConf, reader); err != nil {
|
||||
return nil, fmt.Errorf("failed parsing --set-file data: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Merge together base config file and overrides, prefer the overrides
|
||||
conf := mergeValues(baseConf, overrideConf)
|
||||
|
||||
// Take the patched config and marshal back to YAML
|
||||
return yaml.Marshal(conf)
|
||||
}
|
||||
|
||||
// regex looking for ${...} notation strings
|
||||
var envRegex = regexp.MustCompile(`(?U:\${.*})`)
|
||||
|
||||
// SubEnvVars will look for any environment variables in the passed in string
|
||||
// with the syntax of ${VAR_NAME} and replace that string with ENV[VAR_NAME]
|
||||
func SubEnvVars(s string) string {
|
||||
return subEnvVars(s)
|
||||
}
|
||||
|
||||
func subEnvVars(s string) string {
|
||||
updatedConfig := envRegex.ReplaceAllStringFunc(s, func(s string) string {
|
||||
// Trim off the '${' and '}'
|
||||
if len(s) <= 3 {
|
||||
// This should never happen..
|
||||
return ""
|
||||
}
|
||||
varName := s[2 : len(s)-1]
|
||||
|
||||
// Lookup the variable in the environment. We do not
|
||||
// play by bash rules: if its undefined we'll keep it
|
||||
// as-is, it could be replaced somewhere down the line.
|
||||
// If it's set to "", we'll return that.
|
||||
if lu, ok := os.LookupEnv(varName); ok {
|
||||
return lu
|
||||
}
|
||||
return s
|
||||
})
|
||||
|
||||
return updatedConfig
|
||||
}
|
||||
|
||||
// mergeValues will merge source and destination map, preferring values from the source map
|
||||
func mergeValues(dest map[string]any, src map[string]any) map[string]any {
|
||||
for k, v := range src {
|
||||
// If the key doesn't exist already, then just set the key to that value
|
||||
if _, exists := dest[k]; !exists {
|
||||
dest[k] = v
|
||||
continue
|
||||
}
|
||||
nextMap, ok := v.(map[string]any)
|
||||
// If it isn't another map, overwrite the value
|
||||
if !ok {
|
||||
dest[k] = v
|
||||
continue
|
||||
}
|
||||
// Edge case: If the key exists in the destination, but isn't a map
|
||||
destMap, isMap := dest[k].(map[string]any)
|
||||
// If the source map has a map for this key, prefer it
|
||||
if !isMap {
|
||||
dest[k] = v
|
||||
continue
|
||||
}
|
||||
// If we got to this point, it is a map in both, so merge them
|
||||
dest[k] = mergeValues(destMap, nextMap)
|
||||
}
|
||||
return dest
|
||||
}
|
||||
+21
-4
@@ -2,10 +2,12 @@
|
||||
// which supports lookups, sets, appends, insertions, and deletions.
|
||||
package bitvector
|
||||
|
||||
import "slices"
|
||||
|
||||
// A BitVector is a variable sized vector of bits. It supports
|
||||
// lookups, sets, appends, insertions, and deletions.
|
||||
//
|
||||
// This class is not thread safe.
|
||||
// Operations are not thread safe.
|
||||
type BitVector struct {
|
||||
data []byte
|
||||
length int
|
||||
@@ -14,10 +16,25 @@ type BitVector struct {
|
||||
// NewBitVector creates and initializes a new bit vector with length
|
||||
// elements, using data as its initial contents.
|
||||
func NewBitVector(data []byte, length int) *BitVector {
|
||||
return &BitVector{
|
||||
data: data,
|
||||
length: length,
|
||||
return &BitVector{data: data, length: length}
|
||||
}
|
||||
|
||||
func (vector *BitVector) Clear() *BitVector {
|
||||
if vector == nil {
|
||||
return nil
|
||||
}
|
||||
clear(vector.data)
|
||||
vector.length = 0
|
||||
|
||||
return vector
|
||||
}
|
||||
|
||||
func (vector *BitVector) Reset(size, length int) *BitVector {
|
||||
clear(vector.data)
|
||||
vector.data = slices.Grow(vector.data, size)[:size]
|
||||
vector.length = length
|
||||
|
||||
return vector
|
||||
}
|
||||
|
||||
// Bytes returns a slice of the contents of the bit vector. If the caller changes the returned slice,
|
||||
|
||||
+161
-101
@@ -148,13 +148,17 @@ package edittree
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/open-policy-agent/opa/internal/edittree/bitvector"
|
||||
"github.com/open-policy-agent/opa/v1/ast"
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
var refPool = util.NewSlicePool[*ast.Term](1)
|
||||
|
||||
var editTreePool = util.NewSyncPool[EditTree]()
|
||||
|
||||
// Deletions are encoded with a nil value pointer.
|
||||
type EditTree struct {
|
||||
value *ast.Term
|
||||
@@ -171,31 +175,66 @@ func NewEditTree(term *ast.Term) *EditTree {
|
||||
return nil
|
||||
}
|
||||
|
||||
var tree EditTree
|
||||
return initForTerm(&EditTree{}, term)
|
||||
}
|
||||
|
||||
func EditTreeFromPool(term *ast.Term) *EditTree {
|
||||
return initForTerm(editTreePool.Get(), term)
|
||||
}
|
||||
|
||||
func Dispose(e *EditTree) {
|
||||
if e != nil {
|
||||
editTreePool.Put(e.Reset())
|
||||
}
|
||||
}
|
||||
|
||||
func (e *EditTree) Reset() *EditTree {
|
||||
e.value = nil
|
||||
clear(e.childKeys)
|
||||
clear(e.childScalarValues)
|
||||
clear(e.childCompositeValues)
|
||||
|
||||
e.eliminated = e.eliminated.Clear()
|
||||
e.insertions = e.insertions.Clear()
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
func initForTerm(tree *EditTree, term *ast.Term) *EditTree {
|
||||
tree.value = term
|
||||
|
||||
switch x := term.Value.(type) {
|
||||
case ast.Object, ast.Set:
|
||||
tree = EditTree{
|
||||
value: term,
|
||||
childKeys: map[int]*ast.Term{},
|
||||
childScalarValues: map[int]*ast.Term{},
|
||||
childCompositeValues: map[int]*EditTree{},
|
||||
if tree.childKeys == nil {
|
||||
tree.childKeys = map[int]*ast.Term{}
|
||||
}
|
||||
if tree.childScalarValues == nil {
|
||||
tree.childScalarValues = map[int]*ast.Term{}
|
||||
}
|
||||
if tree.childCompositeValues == nil {
|
||||
tree.childCompositeValues = map[int]*EditTree{}
|
||||
}
|
||||
case *ast.Array:
|
||||
tree = EditTree{
|
||||
value: term,
|
||||
childScalarValues: map[int]*ast.Term{},
|
||||
childCompositeValues: map[int]*EditTree{},
|
||||
if tree.childScalarValues == nil {
|
||||
tree.childScalarValues = map[int]*ast.Term{}
|
||||
}
|
||||
if tree.childCompositeValues == nil {
|
||||
tree.childCompositeValues = map[int]*EditTree{}
|
||||
}
|
||||
bytesLength := ((x.Len() - 1) / 8) + 1 // How many bytes to use for the bit-vectors.
|
||||
tree.eliminated = bitvector.NewBitVector(make([]byte, bytesLength), x.Len())
|
||||
tree.insertions = bitvector.NewBitVector(make([]byte, bytesLength), x.Len())
|
||||
default:
|
||||
tree = EditTree{
|
||||
value: term,
|
||||
if tree.eliminated == nil {
|
||||
tree.eliminated = bitvector.NewBitVector(make([]byte, bytesLength), x.Len())
|
||||
} else {
|
||||
tree.eliminated = tree.eliminated.Reset(bytesLength, x.Len())
|
||||
}
|
||||
if tree.insertions == nil {
|
||||
tree.insertions = bitvector.NewBitVector(make([]byte, bytesLength), x.Len())
|
||||
} else {
|
||||
tree.insertions = tree.insertions.Reset(bytesLength, x.Len())
|
||||
}
|
||||
}
|
||||
|
||||
return &tree
|
||||
return tree
|
||||
}
|
||||
|
||||
// Returns correct (collision-resolved) hash for this term + whether or not
|
||||
@@ -231,17 +270,14 @@ func isComposite(t *ast.Term) bool {
|
||||
}
|
||||
}
|
||||
|
||||
//gcassert:inline
|
||||
func (e *EditTree) setChildKey(hash int, key *ast.Term) {
|
||||
e.childKeys[hash] = key
|
||||
}
|
||||
|
||||
//gcassert:inline
|
||||
func (e *EditTree) setChildScalarValue(hash int, value *ast.Term) {
|
||||
e.childScalarValues[hash] = value
|
||||
}
|
||||
|
||||
//gcassert:inline
|
||||
func (e *EditTree) setChildCompositeValue(hash int, child *EditTree) {
|
||||
e.childCompositeValues[hash] = child
|
||||
}
|
||||
@@ -278,11 +314,10 @@ func (e *EditTree) Insert(key, value *ast.Term) (*EditTree, error) {
|
||||
// We only collapse this Set-typed node if a composite type is involved.
|
||||
if isComposite(key) {
|
||||
// TODO: Investigate re-rendering *only* the immediate composite children.
|
||||
collapsed := e.Render()
|
||||
e.value = collapsed
|
||||
e.childKeys = map[int]*ast.Term{}
|
||||
e.childScalarValues = map[int]*ast.Term{}
|
||||
e.childCompositeValues = map[int]*EditTree{}
|
||||
e.value = e.Render()
|
||||
clear(e.childKeys)
|
||||
clear(e.childScalarValues)
|
||||
clear(e.childCompositeValues)
|
||||
}
|
||||
return e.unsafeInsertSet(key, value), nil
|
||||
case *ast.Array:
|
||||
@@ -301,12 +336,13 @@ func (e *EditTree) Insert(key, value *ast.Term) (*EditTree, error) {
|
||||
}
|
||||
|
||||
func (e *EditTree) unsafeInsertObject(key, value *ast.Term) *EditTree {
|
||||
child := NewEditTree(value)
|
||||
keyHash, found := e.getKeyHash(key)
|
||||
if found {
|
||||
e.deleteChildValue(keyHash)
|
||||
}
|
||||
e.setChildKey(keyHash, key)
|
||||
|
||||
child := NewEditTree(value)
|
||||
if isComposite(value) {
|
||||
e.setChildCompositeValue(keyHash, child)
|
||||
} else {
|
||||
@@ -331,10 +367,9 @@ func (e *EditTree) unsafeInsertSet(key, value *ast.Term) *EditTree {
|
||||
}
|
||||
|
||||
func (e *EditTree) unsafeInsertArray(idx int, value *ast.Term) *EditTree {
|
||||
child := NewEditTree(value)
|
||||
// Collect insertion indexes above the insertion site for rewriting.
|
||||
rewritesScalars := []int{}
|
||||
rewritesComposites := []int{}
|
||||
var rewritesScalars, rewritesComposites []int
|
||||
|
||||
for i := idx; i < e.insertions.Length(); i++ {
|
||||
if e.insertions.Element(i) == 1 {
|
||||
if _, ok := e.childScalarValues[i]; ok {
|
||||
@@ -369,6 +404,8 @@ func (e *EditTree) unsafeInsertArray(idx int, value *ast.Term) *EditTree {
|
||||
} else {
|
||||
e.insertions.Insert(1, idx)
|
||||
}
|
||||
|
||||
child := NewEditTree(value)
|
||||
if isComposite(value) {
|
||||
e.setChildCompositeValue(idx, child)
|
||||
} else {
|
||||
@@ -427,9 +464,9 @@ func (e *EditTree) Delete(key *ast.Term) (*EditTree, error) {
|
||||
// TODO: Investigate re-rendering *only* the immediate composite children.
|
||||
collapsed := e.Render()
|
||||
e.value = collapsed
|
||||
e.childKeys = map[int]*ast.Term{}
|
||||
e.childScalarValues = map[int]*ast.Term{}
|
||||
e.childCompositeValues = map[int]*EditTree{}
|
||||
clear(e.childKeys)
|
||||
clear(e.childScalarValues)
|
||||
clear(e.childCompositeValues)
|
||||
} else {
|
||||
keyHash, found := e.getKeyHash(key)
|
||||
// If child found, replace with delete node. If delete node already existed, error.
|
||||
@@ -539,7 +576,14 @@ func findIndexOfNthZero(n int, bv *bitvector.BitVector) (int, bool) {
|
||||
// Helper function for sets/objects when the key isn't present in either
|
||||
// child map.
|
||||
func (e *EditTree) fallbackDelete(key *ast.Term) (*EditTree, error) {
|
||||
value, err := e.value.Value.Find(ast.Ref{key})
|
||||
// get ref from pool
|
||||
rptr := refPool.Get(1)
|
||||
defer refPool.Put(rptr)
|
||||
|
||||
ref := *rptr
|
||||
ref[0] = key
|
||||
|
||||
value, err := e.value.Value.Find(ref)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot delete child key %v that does not exist", key)
|
||||
}
|
||||
@@ -595,7 +639,7 @@ func (e *EditTree) Unfold(path ast.Ref) (*EditTree, error) {
|
||||
}
|
||||
// Fall back to looking up the key in e.value.
|
||||
// Extend the tree if key is present. Error otherwise.
|
||||
if v, err := x.Find(ast.Ref{path[0]}); err == nil {
|
||||
if v, err := x.Find(path[:1]); err == nil {
|
||||
child, err := e.Insert(path[0], ast.NewTerm(v))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -611,12 +655,11 @@ func (e *EditTree) Unfold(path ast.Ref) (*EditTree, error) {
|
||||
if isComposite(key) {
|
||||
collapsed := e.Render()
|
||||
e.value = collapsed
|
||||
e.childKeys = map[int]*ast.Term{}
|
||||
e.childScalarValues = map[int]*ast.Term{}
|
||||
e.childCompositeValues = map[int]*EditTree{}
|
||||
clear(e.childKeys)
|
||||
clear(e.childScalarValues)
|
||||
clear(e.childCompositeValues)
|
||||
} else {
|
||||
keyHash, found := e.getKeyHash(key)
|
||||
if found {
|
||||
if keyHash, found := e.getKeyHash(key); found {
|
||||
if term, ok := e.childScalarValues[keyHash]; ok {
|
||||
child := NewEditTree(term)
|
||||
return child.Unfold(path[1:])
|
||||
@@ -625,14 +668,14 @@ func (e *EditTree) Unfold(path ast.Ref) (*EditTree, error) {
|
||||
}
|
||||
// Fall back to looking up the key in e.value.
|
||||
// Extend the tree if key is present. Error otherwise.
|
||||
if v, err := e.value.Value.Find(ast.Ref{path[0]}); err == nil {
|
||||
if v, err := e.value.Value.Find(path[:1]); err == nil {
|
||||
child, err := e.Insert(path[0], ast.NewTerm(v))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return child.Unfold(path[1:])
|
||||
}
|
||||
return nil, fmt.Errorf("path %v does not exist in set term %v", ast.Ref{path[0]}, e.value.Value)
|
||||
return nil, fmt.Errorf("path %v does not exist in set term %v", path[:1], e.value.Value)
|
||||
case *ast.Array:
|
||||
idx, err := toIndex(e.insertions.Length(), path[0])
|
||||
if err != nil {
|
||||
@@ -647,13 +690,17 @@ func (e *EditTree) Unfold(path ast.Ref) (*EditTree, error) {
|
||||
}
|
||||
|
||||
idxt := ast.InternedTerm(idx)
|
||||
rptr := refPool.Get(1)
|
||||
defer refPool.Put(rptr)
|
||||
|
||||
ref := *rptr
|
||||
ref[0] = idxt
|
||||
|
||||
// Fall back to looking up the key in e.value.
|
||||
// Extend the tree if key is present. Error otherwise.
|
||||
if v, err := x.Find(ast.Ref{idxt}); err == nil {
|
||||
if v, err := x.Find(ref); err == nil {
|
||||
// TODO: Consider a more efficient "Replace" function that special-cases this for arrays instead?
|
||||
_, err := e.Delete(idxt)
|
||||
if err != nil {
|
||||
if _, err := e.Delete(idxt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
child, err := e.Insert(idxt, ast.NewTerm(v))
|
||||
@@ -662,7 +709,7 @@ func (e *EditTree) Unfold(path ast.Ref) (*EditTree, error) {
|
||||
}
|
||||
return child.Unfold(path[1:])
|
||||
}
|
||||
return nil, fmt.Errorf("path %v does not exist in array term %v", ast.IntNumberTerm(idx), e.value.Value)
|
||||
return nil, fmt.Errorf("path %v does not exist in array term %v", ast.InternedTerm(idx), e.value.Value)
|
||||
default:
|
||||
// Catch all primitive types.
|
||||
return nil, fmt.Errorf("expected composite type for path %v, found value: %v (type: %T)", path[0], x, x)
|
||||
@@ -787,8 +834,7 @@ func (e *EditTree) Render() *ast.Term {
|
||||
if t, ok := e.childScalarValues[i]; ok {
|
||||
out = append(out, t)
|
||||
} else if child, ok := e.childCompositeValues[i]; ok {
|
||||
t := child.Render()
|
||||
out = append(out, t)
|
||||
out = append(out, child.Render())
|
||||
} else {
|
||||
panic(fmt.Errorf("invalid index %d does not exist in array", i))
|
||||
}
|
||||
@@ -810,9 +856,10 @@ func (e *EditTree) InsertAtPath(path ast.Ref, value *ast.Term) (*EditTree, error
|
||||
|
||||
if len(path) == 0 {
|
||||
e.value = value
|
||||
e.childKeys = map[int]*ast.Term{}
|
||||
e.childScalarValues = map[int]*ast.Term{}
|
||||
e.childCompositeValues = map[int]*EditTree{}
|
||||
clear(e.childKeys)
|
||||
clear(e.childScalarValues)
|
||||
clear(e.childCompositeValues)
|
||||
|
||||
if v, ok := value.Value.(*ast.Array); ok {
|
||||
bytesLength := ((v.Len() - 1) / 8) + 1 // How many bytes to use for the bit-vectors.
|
||||
e.eliminated = bitvector.NewBitVector(make([]byte, bytesLength), v.Len())
|
||||
@@ -921,24 +968,19 @@ func (e *EditTree) Exists(path ast.Ref) bool {
|
||||
// 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) {
|
||||
collapsed := e.Render()
|
||||
e.value = collapsed
|
||||
e.childKeys = map[int]*ast.Term{}
|
||||
e.childScalarValues = map[int]*ast.Term{}
|
||||
e.childCompositeValues = map[int]*EditTree{}
|
||||
} else {
|
||||
keyHash, found := e.getKeyHash(key)
|
||||
if found {
|
||||
if _, ok := e.childScalarValues[keyHash]; ok {
|
||||
return len(path) == 1
|
||||
}
|
||||
e.value = e.Render()
|
||||
clear(e.childKeys)
|
||||
clear(e.childScalarValues)
|
||||
clear(e.childCompositeValues)
|
||||
} else if keyHash, found := e.getKeyHash(key); found {
|
||||
if _, ok := e.childScalarValues[keyHash]; ok {
|
||||
return len(path) == 1
|
||||
}
|
||||
}
|
||||
// Fallback if child lookup failed.
|
||||
_, err := e.value.Value.Find(path)
|
||||
return err == nil
|
||||
case *ast.Array:
|
||||
var idx int
|
||||
idx, err := toIndex(e.insertions.Length(), path[0])
|
||||
if err != nil {
|
||||
return false
|
||||
@@ -951,7 +993,16 @@ func (e *EditTree) Exists(path ast.Ref) bool {
|
||||
}
|
||||
// Fallback if child lookup failed.
|
||||
// We have to ensure that the lookup term is a number here, or Find will fail.
|
||||
_, err = x.Find(ast.Ref{ast.InternedTerm(idx)}.Concat(path[1:]))
|
||||
rptr := refPool.Get(len(path))
|
||||
|
||||
ref := *rptr
|
||||
ref[0] = ast.InternedTerm(idx)
|
||||
copy(ref[1:], path[1:])
|
||||
|
||||
_, err = x.Find(ref)
|
||||
|
||||
refPool.Put(rptr)
|
||||
|
||||
return err == nil
|
||||
default:
|
||||
// Catch all primitive types.
|
||||
@@ -978,8 +1029,7 @@ func toIndex(arrayLength int, term *ast.Term) (int, error) {
|
||||
if v == "-" {
|
||||
return arrayLength, nil
|
||||
}
|
||||
num := ast.Number(v)
|
||||
if i, ok = num.Int(); !ok {
|
||||
if i, ok = ast.Number(v).Int(); !ok {
|
||||
return 0, errors.New("invalid string for indexing")
|
||||
}
|
||||
if v != "0" && strings.HasPrefix(string(v), "0") {
|
||||
@@ -1004,6 +1054,14 @@ func (e *EditTree) Filter(paths []ast.Ref) *ast.Term {
|
||||
return nil
|
||||
}
|
||||
|
||||
// term pointer and ref pointer for reuse in lookups and iteration below.
|
||||
tptr, rptr := ast.TermPtrPool.Get(), refPool.Get(1)
|
||||
defer func() {
|
||||
tptr.Value = nil
|
||||
ast.TermPtrPool.Put(tptr)
|
||||
refPool.Put(rptr)
|
||||
}()
|
||||
|
||||
// Separate out keys for this level.
|
||||
// In the event of paths like "a", "a/b", "a/b/c", the "a" path will win out.
|
||||
// Nil keys, such as "" or [], are not permitted. (legacy behavior)
|
||||
@@ -1026,7 +1084,8 @@ func (e *EditTree) Filter(paths []ast.Ref) *ast.Term {
|
||||
renderNow := ast.NewSet(renderNowList...)
|
||||
// Clear everything out of the pathMap that has a renderNow candidate.
|
||||
for k := range pathMap {
|
||||
if renderNow.Contains(ast.NewTerm(k)) {
|
||||
tptr.Value = k
|
||||
if renderNow.Contains(tptr) {
|
||||
delete(pathMap, k)
|
||||
}
|
||||
}
|
||||
@@ -1035,37 +1094,46 @@ func (e *EditTree) Filter(paths []ast.Ref) *ast.Term {
|
||||
switch e.value.Value.(type) {
|
||||
case ast.Object:
|
||||
out := make([][2]*ast.Term, 0, renderNow.Len()+len(pathMap))
|
||||
ref := *rptr
|
||||
|
||||
// Render any finished paths.
|
||||
renderNow.Foreach(func(k *ast.Term) {
|
||||
if e.Exists(ast.Ref{k}) {
|
||||
subtreeResult, _ := e.RenderAtPath(ast.Ref{k})
|
||||
for _, k := range renderNow.Slice() {
|
||||
ref[0] = k
|
||||
if e.Exists(ref) {
|
||||
subtreeResult, _ := e.RenderAtPath(ref)
|
||||
out = append(out, [2]*ast.Term{k, subtreeResult})
|
||||
}
|
||||
})
|
||||
}
|
||||
// Recursively descend remaining paths.
|
||||
for k, p := range pathMap {
|
||||
if e.Exists(ast.Ref{ast.NewTerm(k)}) {
|
||||
child, _ := e.Unfold(ast.Ref{ast.NewTerm(k)})
|
||||
tptr.Value = k
|
||||
ref[0] = tptr
|
||||
if e.Exists(ref) {
|
||||
child, _ := e.Unfold(ref)
|
||||
subtreeResult := child.Filter(p)
|
||||
out = append(out, [2]*ast.Term{ast.NewTerm(k), subtreeResult})
|
||||
}
|
||||
}
|
||||
|
||||
return ast.ObjectTerm(out...)
|
||||
case ast.Set:
|
||||
out := make([]*ast.Term, 0, renderNow.Len()+len(pathMap))
|
||||
ref := *rptr
|
||||
// Render any finished paths.
|
||||
renderNow.Foreach(func(k *ast.Term) {
|
||||
if e.Exists(ast.Ref{k}) {
|
||||
subtreeResult, _ := e.RenderAtPath(ast.Ref{k})
|
||||
for _, k := range renderNow.Slice() {
|
||||
ref[0] = k
|
||||
if e.Exists(ref) {
|
||||
subtreeResult, _ := e.RenderAtPath(ref)
|
||||
out = append(out, subtreeResult)
|
||||
}
|
||||
})
|
||||
}
|
||||
// Recursively descend remaining paths.
|
||||
for k, p := range pathMap {
|
||||
if e.Exists(ast.Ref{ast.NewTerm(k)}) {
|
||||
child, _ := e.Unfold(ast.Ref{ast.NewTerm(k)})
|
||||
subtreeResult := child.Filter(p)
|
||||
out = append(out, subtreeResult)
|
||||
tptr.Value = k
|
||||
ref[0] = tptr
|
||||
if e.Exists(ref) {
|
||||
child, _ := e.Unfold(ref)
|
||||
out = append(out, child.Filter(p))
|
||||
}
|
||||
}
|
||||
return ast.SetTerm(out...)
|
||||
@@ -1073,27 +1141,25 @@ func (e *EditTree) Filter(paths []ast.Ref) *ast.Term {
|
||||
// No early exit here, because we might have just deletes on the
|
||||
// original array. We build a new Array with modified/deleted keys.
|
||||
out := make([]*ast.Term, 0, renderNow.Len()+len(pathMap))
|
||||
// Sort array indexes before descending.
|
||||
idxList := make([]*ast.Term, 0, len(pathMap))
|
||||
renderNow.Foreach(func(k *ast.Term) {
|
||||
idxList = append(idxList, k)
|
||||
})
|
||||
idxList := append(make([]*ast.Term, 0, renderNow.Len()+len(pathMap)), renderNow.Slice()...)
|
||||
for k := range pathMap {
|
||||
idxList = append(idxList, ast.NewTerm(k))
|
||||
}
|
||||
sort.Sort(termSlice(idxList))
|
||||
// Render child or recursively descend as needed.
|
||||
for i := range idxList {
|
||||
|
||||
ref := *rptr
|
||||
|
||||
// Render child or recursively descend sorted indexes as needed.
|
||||
for i := range util.SortedFunc(idxList, ast.TermValueCompare) {
|
||||
k := idxList[i]
|
||||
if renderNow.Contains(k) {
|
||||
if e.Exists(ast.Ref{k}) {
|
||||
subtreeResult, _ := e.RenderAtPath(ast.Ref{k})
|
||||
ref[0] = k
|
||||
if e.Exists(ref) {
|
||||
if renderNow.Contains(k) {
|
||||
subtreeResult, _ := e.RenderAtPath(ref)
|
||||
out = append(out, subtreeResult)
|
||||
} else {
|
||||
child, _ := e.Unfold(ref)
|
||||
out = append(out, child.Filter(pathMap[k.Value]))
|
||||
}
|
||||
} else if e.Exists(ast.Ref{k}) {
|
||||
child, _ := e.Unfold(ast.Ref{k})
|
||||
subtreeResult := child.Filter(pathMap[k.Value])
|
||||
out = append(out, subtreeResult)
|
||||
}
|
||||
}
|
||||
return ast.ArrayTerm(out...)
|
||||
@@ -1101,9 +1167,3 @@ func (e *EditTree) Filter(paths []ast.Ref) *ast.Term {
|
||||
return e.value
|
||||
}
|
||||
}
|
||||
|
||||
type termSlice []*ast.Term
|
||||
|
||||
func (s termSlice) Less(i, j int) bool { return ast.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) }
|
||||
|
||||
+1
-1
@@ -2070,7 +2070,7 @@ func (p *Planner) planRefDataExtent(virtual *ruletrie, base *baseptr, iter plani
|
||||
}
|
||||
}
|
||||
if anyKeyNonGround {
|
||||
var rules []*ast.Rule
|
||||
rules := make([]*ast.Rule, 0, len(virtual.Children()))
|
||||
for _, key := range virtual.Children() {
|
||||
// TODO(sr): skip functions
|
||||
rules = append(rules, virtual.Get(key).Rules()...)
|
||||
|
||||
-258
@@ -1,258 +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 init is an internal package with helpers for data and policy loading during initialization.
|
||||
package init
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
storedversion "github.com/open-policy-agent/opa/internal/version"
|
||||
"github.com/open-policy-agent/opa/v1/ast"
|
||||
"github.com/open-policy-agent/opa/v1/bundle"
|
||||
"github.com/open-policy-agent/opa/v1/loader"
|
||||
"github.com/open-policy-agent/opa/v1/metrics"
|
||||
"github.com/open-policy-agent/opa/v1/storage"
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// InsertAndCompileOptions contains the input for the operation.
|
||||
type InsertAndCompileOptions struct {
|
||||
Store storage.Store
|
||||
Txn storage.Transaction
|
||||
Files loader.Result
|
||||
Bundles map[string]*bundle.Bundle
|
||||
MaxErrors int
|
||||
EnablePrintStatements bool
|
||||
ParserOptions ast.ParserOptions
|
||||
BundleActivatorPlugin string
|
||||
}
|
||||
|
||||
// InsertAndCompileResult contains the output of the operation.
|
||||
type InsertAndCompileResult struct {
|
||||
Compiler *ast.Compiler
|
||||
Metrics metrics.Metrics
|
||||
}
|
||||
|
||||
// InsertAndCompile writes data and policy into the store and returns a compiler for the
|
||||
// store contents.
|
||||
func InsertAndCompile(ctx context.Context, opts InsertAndCompileOptions) (*InsertAndCompileResult, error) {
|
||||
if len(opts.Files.Documents) > 0 {
|
||||
if err := opts.Store.Write(ctx, opts.Txn, storage.AddOp, storage.RootPath, opts.Files.Documents); err != nil {
|
||||
return nil, fmt.Errorf("storage error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
policies := make(map[string]*ast.Module, len(opts.Files.Modules))
|
||||
|
||||
for id, parsed := range opts.Files.Modules {
|
||||
policies[id] = parsed.Parsed
|
||||
}
|
||||
|
||||
compiler := ast.NewCompiler().
|
||||
WithDefaultRegoVersion(opts.ParserOptions.RegoVersion).
|
||||
SetErrorLimit(opts.MaxErrors).
|
||||
WithPathConflictsCheck(storage.NonEmpty(ctx, opts.Store, opts.Txn)).
|
||||
WithEnablePrintStatements(opts.EnablePrintStatements)
|
||||
m := metrics.New()
|
||||
|
||||
activation := &bundle.ActivateOpts{
|
||||
Ctx: ctx,
|
||||
Store: opts.Store,
|
||||
Txn: opts.Txn,
|
||||
Compiler: compiler,
|
||||
Metrics: m,
|
||||
Bundles: opts.Bundles,
|
||||
ExtraModules: policies,
|
||||
ParserOptions: opts.ParserOptions,
|
||||
Plugin: opts.BundleActivatorPlugin,
|
||||
}
|
||||
|
||||
err := bundle.Activate(activation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Policies in bundles will have already been added to the store, but
|
||||
// modules loaded outside of bundles will need to be added manually.
|
||||
for id, parsed := range opts.Files.Modules {
|
||||
if err := opts.Store.UpsertPolicy(ctx, opts.Txn, id, parsed.Raw); err != nil {
|
||||
return nil, fmt.Errorf("storage error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Set the version in the store last to prevent data files from overwriting.
|
||||
if err := storedversion.Write(ctx, opts.Store, opts.Txn); err != nil {
|
||||
return nil, fmt.Errorf("storage error: %w", err)
|
||||
}
|
||||
|
||||
return &InsertAndCompileResult{Compiler: compiler, Metrics: m}, nil
|
||||
}
|
||||
|
||||
// LoadPathsResult contains the output loading a set of paths.
|
||||
type LoadPathsResult struct {
|
||||
Bundles map[string]*bundle.Bundle
|
||||
Files loader.Result
|
||||
}
|
||||
|
||||
// WalkPathsResult contains the output loading a set of paths.
|
||||
type WalkPathsResult struct {
|
||||
BundlesLoader []BundleLoader
|
||||
FileDescriptors []*Descriptor
|
||||
}
|
||||
|
||||
// BundleLoader contains information about files in a bundle
|
||||
type BundleLoader struct {
|
||||
DirectoryLoader bundle.DirectoryLoader
|
||||
IsDir bool
|
||||
}
|
||||
|
||||
// Descriptor contains information about a file
|
||||
type Descriptor struct {
|
||||
Root string
|
||||
Path string
|
||||
}
|
||||
|
||||
// LoadPaths reads data and policy from the given paths and returns a set of bundles or
|
||||
// raw loader file results.
|
||||
func LoadPaths(paths []string,
|
||||
filter loader.Filter,
|
||||
asBundle bool,
|
||||
bvc *bundle.VerificationConfig,
|
||||
skipVerify bool,
|
||||
bundleLazyLoading bool,
|
||||
processAnnotations bool,
|
||||
caps *ast.Capabilities,
|
||||
fsys fs.FS) (*LoadPathsResult, error) {
|
||||
return LoadPathsForRegoVersion(ast.RegoV0, paths, filter, asBundle, bvc, skipVerify, bundleLazyLoading, processAnnotations, false, caps, fsys)
|
||||
}
|
||||
|
||||
func LoadPathsForRegoVersion(regoVersion ast.RegoVersion,
|
||||
paths []string,
|
||||
filter loader.Filter,
|
||||
asBundle bool,
|
||||
bvc *bundle.VerificationConfig,
|
||||
skipVerify bool,
|
||||
bundleLazyLoading bool,
|
||||
processAnnotations bool,
|
||||
followSymlinks bool,
|
||||
caps *ast.Capabilities,
|
||||
fsys fs.FS) (*LoadPathsResult, error) {
|
||||
|
||||
if caps == nil {
|
||||
caps = ast.CapabilitiesForThisVersion()
|
||||
}
|
||||
|
||||
// tar.gz files are automatically loaded as bundles
|
||||
var likelyBundles, nonBundlePaths []string
|
||||
if !asBundle {
|
||||
likelyBundles, nonBundlePaths = splitByTarGzExt(paths)
|
||||
paths = likelyBundles
|
||||
}
|
||||
|
||||
var result LoadPathsResult
|
||||
var err error
|
||||
if asBundle || len(likelyBundles) > 0 {
|
||||
result.Bundles = make(map[string]*bundle.Bundle, len(paths))
|
||||
for _, path := range paths {
|
||||
result.Bundles[path], err = loader.NewFileLoader().
|
||||
WithFS(fsys).
|
||||
WithBundleVerificationConfig(bvc).
|
||||
WithSkipBundleVerification(skipVerify).
|
||||
WithBundleLazyLoadingMode(bundleLazyLoading).
|
||||
WithFilter(filter).
|
||||
WithProcessAnnotation(processAnnotations).
|
||||
WithCapabilities(caps).
|
||||
WithRegoVersion(regoVersion).
|
||||
WithFollowSymlinks(followSymlinks).
|
||||
AsBundle(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if asBundle {
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
files, err := loader.NewFileLoader().
|
||||
WithFS(fsys).
|
||||
WithBundleLazyLoadingMode(bundleLazyLoading).
|
||||
WithProcessAnnotation(processAnnotations).
|
||||
WithCapabilities(caps).
|
||||
WithRegoVersion(regoVersion).
|
||||
Filtered(nonBundlePaths, filter)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result.Files = *files
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// splitByTarGzExt splits the paths in 2 groups. Ones with .tar.gz and another with
|
||||
// non .tar.gz extensions.
|
||||
func splitByTarGzExt(paths []string) (targzs []string, nonTargzs []string) {
|
||||
for _, path := range paths {
|
||||
if strings.HasSuffix(path, ".tar.gz") {
|
||||
targzs = append(targzs, path)
|
||||
} else {
|
||||
nonTargzs = append(nonTargzs, path)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// WalkPaths reads data and policy from the given paths and returns a set of bundle directory loaders
|
||||
// or descriptors that contain information about files.
|
||||
func WalkPaths(paths []string, filter loader.Filter, asBundle bool) (*WalkPathsResult, error) {
|
||||
|
||||
var result WalkPathsResult
|
||||
|
||||
if asBundle {
|
||||
result.BundlesLoader = make([]BundleLoader, len(paths))
|
||||
for i, path := range paths {
|
||||
bundleLoader, isDir, err := loader.GetBundleDirectoryLoader(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result.BundlesLoader[i] = BundleLoader{
|
||||
DirectoryLoader: bundleLoader,
|
||||
IsDir: isDir,
|
||||
}
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
result.FileDescriptors = []*Descriptor{}
|
||||
for _, path := range paths {
|
||||
filePaths, err := loader.FilteredPaths([]string{path}, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, fp := range filePaths {
|
||||
// Trim off the root directory and return path as if chrooted
|
||||
cleanedPath := strings.TrimPrefix(fp, path)
|
||||
if path == "." && filepath.Base(fp) == bundle.ManifestExt {
|
||||
cleanedPath = fp
|
||||
}
|
||||
|
||||
result.FileDescriptors = append(result.FileDescriptors, &Descriptor{
|
||||
Root: path,
|
||||
Path: util.WithPrefix(cleanedPath, "/"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
Package strvals provides tools for working with strval lines.
|
||||
|
||||
OPA runtime config supports a compressed format for YAML settings which we call strvals.
|
||||
The format is roughly like this:
|
||||
|
||||
name=value,topname.subname=value
|
||||
|
||||
The above is equivalent to the YAML document
|
||||
|
||||
name: value
|
||||
topname:
|
||||
subname: value
|
||||
|
||||
This package provides a parser and utilities for converting the strvals format
|
||||
to other formats.
|
||||
*/
|
||||
package strvals
|
||||
-429
@@ -1,429 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package strvals
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// ErrNotList indicates that a non-list was treated as a list.
|
||||
var ErrNotList = errors.New("not a list")
|
||||
|
||||
// MaxIndex is the maximum index that will be allowed by setIndex.
|
||||
// The default value 65536 = 1024 * 64
|
||||
const MaxIndex = 65536
|
||||
|
||||
// ToYAML takes a string of arguments and converts to a YAML document.
|
||||
func ToYAML(s string) (string, error) {
|
||||
m, err := Parse(s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
d, err := yaml.Marshal(m)
|
||||
return string(d), err
|
||||
}
|
||||
|
||||
// Parse parses a set line.
|
||||
//
|
||||
// A set line is of the form name1=value1,name2=value2
|
||||
func Parse(s string) (map[string]any, error) {
|
||||
vals := map[string]any{}
|
||||
scanner := bytes.NewBufferString(s)
|
||||
t := newParser(scanner, vals, false)
|
||||
err := t.parse()
|
||||
return vals, err
|
||||
}
|
||||
|
||||
// ParseString parses a set line and forces a string value.
|
||||
//
|
||||
// A set line is of the form name1=value1,name2=value2
|
||||
func ParseString(s string) (map[string]any, error) {
|
||||
vals := map[string]any{}
|
||||
scanner := bytes.NewBufferString(s)
|
||||
t := newParser(scanner, vals, true)
|
||||
err := t.parse()
|
||||
return vals, err
|
||||
}
|
||||
|
||||
// ParseInto parses a strvals line and merges the result into dest.
|
||||
//
|
||||
// If the strval string has a key that exists in dest, it overwrites the
|
||||
// dest version.
|
||||
func ParseInto(s string, dest map[string]any) error {
|
||||
scanner := bytes.NewBufferString(s)
|
||||
t := newParser(scanner, dest, false)
|
||||
return t.parse()
|
||||
}
|
||||
|
||||
// ParseIntoFile parses a filevals line and merges the result into dest.
|
||||
//
|
||||
// This method always returns a string as the value.
|
||||
func ParseIntoFile(s string, dest map[string]any, runesToVal runesToVal) error {
|
||||
scanner := bytes.NewBufferString(s)
|
||||
t := newFileParser(scanner, dest, runesToVal)
|
||||
return t.parse()
|
||||
}
|
||||
|
||||
// ParseIntoString parses a strvals line and merges the result into dest.
|
||||
//
|
||||
// This method always returns a string as the value.
|
||||
func ParseIntoString(s string, dest map[string]any) error {
|
||||
scanner := bytes.NewBufferString(s)
|
||||
t := newParser(scanner, dest, true)
|
||||
return t.parse()
|
||||
}
|
||||
|
||||
// parser is a simple parser that takes a strvals line and parses it into a
|
||||
// map representation.
|
||||
//
|
||||
// where sc is the source of the original data being parsed
|
||||
// where data is the final parsed data from the parses with correct types
|
||||
// where st is a boolean to figure out if we're forcing it to parse values as string
|
||||
type parser struct {
|
||||
sc *bytes.Buffer
|
||||
data map[string]any
|
||||
runesToVal runesToVal
|
||||
}
|
||||
|
||||
type runesToVal func([]rune) (any, error)
|
||||
|
||||
func newParser(sc *bytes.Buffer, data map[string]any, stringBool bool) *parser {
|
||||
rs2v := func(rs []rune) (any, error) {
|
||||
return typedVal(rs, stringBool), nil
|
||||
}
|
||||
return &parser{sc: sc, data: data, runesToVal: rs2v}
|
||||
}
|
||||
|
||||
func newFileParser(sc *bytes.Buffer, data map[string]any, runesToVal runesToVal) *parser {
|
||||
return &parser{sc: sc, data: data, runesToVal: runesToVal}
|
||||
}
|
||||
|
||||
func (t *parser) parse() error {
|
||||
for {
|
||||
err := t.key(t.data)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func runeSet(r []rune) map[rune]bool {
|
||||
s := make(map[rune]bool, len(r))
|
||||
for _, rr := range r {
|
||||
s[rr] = true
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (t *parser) key(data map[string]any) error {
|
||||
stop := runeSet([]rune{'=', '[', ',', '.'})
|
||||
for {
|
||||
switch k, last, err := runesUntil(t.sc, stop); {
|
||||
case err != nil:
|
||||
if len(k) == 0 {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("key %q has no value", string(k))
|
||||
case last == '[':
|
||||
// We are in a list index context, so we need to set an index.
|
||||
i, err := t.keyIndex()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing index: %s", err)
|
||||
}
|
||||
kk := string(k)
|
||||
// Find or create target list
|
||||
list := []any{}
|
||||
if _, ok := data[kk]; ok {
|
||||
list = data[kk].([]any)
|
||||
}
|
||||
|
||||
// Now we need to get the value after the ].
|
||||
list, err = t.listItem(list, i)
|
||||
set(data, kk, list)
|
||||
return err
|
||||
case last == '=':
|
||||
// End of key. Consume =, Get value.
|
||||
// FIXME: Get value list first
|
||||
vl, e := t.valList()
|
||||
switch e {
|
||||
case nil:
|
||||
set(data, string(k), vl)
|
||||
return nil
|
||||
case io.EOF:
|
||||
set(data, string(k), "")
|
||||
return e
|
||||
case ErrNotList:
|
||||
rs, e := t.val()
|
||||
if e != nil && e != io.EOF {
|
||||
return e
|
||||
}
|
||||
v, e := t.runesToVal(rs)
|
||||
set(data, string(k), v)
|
||||
return e
|
||||
default:
|
||||
return e
|
||||
}
|
||||
|
||||
case last == ',':
|
||||
// No value given. Set the value to empty string. Return error.
|
||||
set(data, string(k), "")
|
||||
return fmt.Errorf("key %q has no value (cannot end with ,)", string(k))
|
||||
case last == '.':
|
||||
// First, create or find the target map.
|
||||
inner := map[string]any{}
|
||||
if _, ok := data[string(k)]; ok {
|
||||
inner = data[string(k)].(map[string]any)
|
||||
}
|
||||
|
||||
// Recurse
|
||||
e := t.key(inner)
|
||||
if len(inner) == 0 {
|
||||
return fmt.Errorf("key map %q has no value", string(k))
|
||||
}
|
||||
set(data, string(k), inner)
|
||||
return e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func set(data map[string]any, key string, val any) {
|
||||
// If key is empty, don't set it.
|
||||
if len(key) == 0 {
|
||||
return
|
||||
}
|
||||
data[key] = val
|
||||
}
|
||||
|
||||
func setIndex(list []any, index int, val any) (l2 []any, err error) {
|
||||
// There are possible index values that are out of range on a target system
|
||||
// causing a panic. This will catch the panic and return an error instead.
|
||||
// The value of the index that causes a panic varies from system to system.
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("error processing index %d: %s", index, r)
|
||||
}
|
||||
}()
|
||||
|
||||
if index < 0 {
|
||||
return list, fmt.Errorf("negative %d index not allowed", index)
|
||||
}
|
||||
if index > MaxIndex {
|
||||
return list, fmt.Errorf("index of %d is greater than maximum supported index of %d", index, MaxIndex)
|
||||
}
|
||||
if len(list) <= index {
|
||||
newlist := make([]any, index+1)
|
||||
copy(newlist, list)
|
||||
list = newlist
|
||||
}
|
||||
list[index] = val
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (t *parser) keyIndex() (int, error) {
|
||||
// First, get the key.
|
||||
stop := runeSet([]rune{']'})
|
||||
v, _, err := runesUntil(t.sc, stop)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// v should be the index
|
||||
return strconv.Atoi(string(v))
|
||||
|
||||
}
|
||||
func (t *parser) listItem(list []any, i int) ([]any, error) {
|
||||
if i < 0 {
|
||||
return list, fmt.Errorf("negative %d index not allowed", i)
|
||||
}
|
||||
stop := runeSet([]rune{'[', '.', '='})
|
||||
switch k, last, err := runesUntil(t.sc, stop); {
|
||||
case len(k) > 0:
|
||||
return list, fmt.Errorf("unexpected data at end of array index: %q", k)
|
||||
case err != nil:
|
||||
return list, err
|
||||
case last == '=':
|
||||
vl, e := t.valList()
|
||||
switch e {
|
||||
case nil:
|
||||
return setIndex(list, i, vl)
|
||||
case io.EOF:
|
||||
return setIndex(list, i, "")
|
||||
case ErrNotList:
|
||||
rs, e := t.val()
|
||||
if e != nil && e != io.EOF {
|
||||
return list, e
|
||||
}
|
||||
v, e := t.runesToVal(rs)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
return setIndex(list, i, v)
|
||||
default:
|
||||
return list, e
|
||||
}
|
||||
case last == '[':
|
||||
// now we have a nested list. Read the index and handle.
|
||||
i, err := t.keyIndex()
|
||||
if err != nil {
|
||||
return list, fmt.Errorf("error parsing index: %s", err)
|
||||
}
|
||||
// Now we need to get the value after the ].
|
||||
list2, err := t.listItem(list, i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return setIndex(list, i, list2)
|
||||
case last == '.':
|
||||
// We have a nested object. Send to t.key
|
||||
inner := map[string]any{}
|
||||
if len(list) > i {
|
||||
var ok bool
|
||||
inner, ok = list[i].(map[string]any)
|
||||
if !ok {
|
||||
// We have indices out of order. Initialize empty value.
|
||||
list[i] = map[string]any{}
|
||||
inner = list[i].(map[string]any)
|
||||
}
|
||||
}
|
||||
|
||||
// Recurse
|
||||
e := t.key(inner)
|
||||
if e != nil {
|
||||
return list, e
|
||||
}
|
||||
return setIndex(list, i, inner)
|
||||
default:
|
||||
return nil, fmt.Errorf("parse error: unexpected token %v", last)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *parser) val() ([]rune, error) {
|
||||
stop := runeSet([]rune{','})
|
||||
v, _, err := runesUntil(t.sc, stop)
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (t *parser) valList() ([]any, error) {
|
||||
r, _, e := t.sc.ReadRune()
|
||||
if e != nil {
|
||||
return []any{}, e
|
||||
}
|
||||
|
||||
if r != '{' {
|
||||
e = t.sc.UnreadRune()
|
||||
if e != nil {
|
||||
return []any{}, e
|
||||
}
|
||||
return []any{}, ErrNotList
|
||||
}
|
||||
|
||||
list := []any{}
|
||||
stop := runeSet([]rune{',', '}'})
|
||||
for {
|
||||
switch rs, last, err := runesUntil(t.sc, stop); {
|
||||
case err != nil:
|
||||
if err == io.EOF {
|
||||
err = errors.New("list must terminate with '}'")
|
||||
}
|
||||
return list, err
|
||||
case last == '}':
|
||||
// If this is followed by ',', consume it.
|
||||
if r, _, e := t.sc.ReadRune(); e == nil && r != ',' {
|
||||
e = t.sc.UnreadRune()
|
||||
if e != nil {
|
||||
return []any{}, e
|
||||
}
|
||||
}
|
||||
v, e := t.runesToVal(rs)
|
||||
list = append(list, v)
|
||||
return list, e
|
||||
case last == ',':
|
||||
v, e := t.runesToVal(rs)
|
||||
if e != nil {
|
||||
return list, e
|
||||
}
|
||||
list = append(list, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runesUntil(in io.RuneReader, stop map[rune]bool) ([]rune, rune, error) {
|
||||
var v []rune
|
||||
for {
|
||||
switch r, _, e := in.ReadRune(); {
|
||||
case e != nil:
|
||||
return v, r, e
|
||||
case inMap(r, stop):
|
||||
return v, r, nil
|
||||
case r == '\\':
|
||||
next, _, e := in.ReadRune()
|
||||
if e != nil {
|
||||
return v, next, e
|
||||
}
|
||||
v = append(v, next)
|
||||
default:
|
||||
v = append(v, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func inMap(k rune, m map[rune]bool) bool {
|
||||
_, ok := m[k]
|
||||
return ok
|
||||
}
|
||||
|
||||
func typedVal(v []rune, st bool) any {
|
||||
val := string(v)
|
||||
|
||||
if st {
|
||||
return val
|
||||
}
|
||||
|
||||
if strings.EqualFold(val, "true") {
|
||||
return true
|
||||
}
|
||||
|
||||
if strings.EqualFold(val, "false") {
|
||||
return false
|
||||
}
|
||||
|
||||
if strings.EqualFold(val, "null") {
|
||||
return struct{}{}
|
||||
}
|
||||
|
||||
if strings.EqualFold(val, "0") {
|
||||
return int64(0)
|
||||
}
|
||||
|
||||
// If this value does not start with zero, try parsing it to an int
|
||||
if len(val) != 0 && val[0] != '0' {
|
||||
if iv, err := strconv.ParseInt(val, 10, 64); err == nil {
|
||||
return iv
|
||||
}
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
-210
@@ -1,210 +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 versioncheck provides functions to check for the latest OPA release version from GitHub.
|
||||
package versioncheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/open-policy-agent/opa/internal/semver"
|
||||
"github.com/open-policy-agent/opa/v1/keys"
|
||||
"github.com/open-policy-agent/opa/v1/logging"
|
||||
"github.com/open-policy-agent/opa/v1/version"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/plugins/rest"
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// ExternalServiceURL is the base HTTP URL for a github instance used
|
||||
// to query for more recent version.
|
||||
// If not otherwise specified, it will use the hard-coded default, api.github.com.
|
||||
// GHRepo is the repository to use, and defaults to "open-policy-agent/opa"
|
||||
//
|
||||
// Override at build time via:
|
||||
//
|
||||
// -ldflags "-X github.com/open-policy-agent/opa/internal/versioncheck.ExternalServiceURL=<url>"
|
||||
// -ldflags "-X github.com/open-policy-agent/opa/internal/versioncheck.GHRepo=<url>"
|
||||
//
|
||||
// ExternalServiceURL will be overridden if the OPA_VERSION_CHECK_SERVICE_URL environment variable
|
||||
// is provided.
|
||||
var ExternalServiceURL = "https://api.github.com"
|
||||
var GHRepo = "open-policy-agent/opa"
|
||||
|
||||
// Checker checks for the latest OPA release version
|
||||
type Checker interface {
|
||||
LatestVersion(ctx context.Context) (*DataResponse, error)
|
||||
RegisterGatherer(key string, f Gatherer)
|
||||
}
|
||||
|
||||
// Gatherer represents a mechanism to inject additional data (currently unused for version checking)
|
||||
type Gatherer func(ctx context.Context) (any, error)
|
||||
|
||||
// DataResponse represents the data returned by the version check
|
||||
type DataResponse struct {
|
||||
Latest ReleaseDetails `json:"latest"`
|
||||
}
|
||||
|
||||
// ReleaseDetails holds information about the latest OPA release
|
||||
type ReleaseDetails struct {
|
||||
Download string `json:"download,omitempty"` // link to download the OPA release
|
||||
ReleaseNotes string `json:"release_notes,omitempty"` // link to the OPA release notes
|
||||
LatestRelease string `json:"latest_release,omitempty"` // latest OPA released version
|
||||
OPAUpToDate bool `json:"opa_up_to_date,omitempty"` // is running OPA version greater than or equal to the latest released
|
||||
}
|
||||
|
||||
// Options supplies parameters to the version checker.
|
||||
type Options struct {
|
||||
Logger logging.Logger
|
||||
}
|
||||
|
||||
type GitHubVersionChecker struct {
|
||||
client rest.Client
|
||||
}
|
||||
|
||||
type GitHubRelease struct {
|
||||
TagName string `json:"tag_name,omitempty"` // latest OPA release tag
|
||||
ReleaseNotes string `json:"html_url,omitempty"` // link to the OPA release notes
|
||||
Download string `json:"assets_url,omitempty"` // link to download the OPA release
|
||||
}
|
||||
|
||||
// New returns an instance of the Checker
|
||||
func New(opts Options) (Checker, error) {
|
||||
url := os.Getenv("OPA_VERSION_CHECK_SERVICE_URL")
|
||||
if url == "" {
|
||||
url = ExternalServiceURL
|
||||
}
|
||||
|
||||
// Set a generic User-Agent to avoid sending version/platform information about the user's OPA instance.
|
||||
// This ensures we only retrieve version information without transmitting any identifying data.
|
||||
restConfig := fmt.Appendf(nil, `{
|
||||
"url": %q,
|
||||
"headers": {
|
||||
"User-Agent": "OPA-Version-Checker"
|
||||
}
|
||||
}`, url)
|
||||
|
||||
client, err := rest.New(restConfig, map[string]*keys.Config{}, rest.Logger(opts.Logger))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := GitHubVersionChecker{client: client}
|
||||
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
// LatestVersion queries the GitHub API to check for the latest OPA release version
|
||||
func (r *GitHubVersionChecker) LatestVersion(ctx context.Context) (*DataResponse, error) {
|
||||
rCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
resp, err := r.client.Do(rCtx, "GET", fmt.Sprintf("/repos/%s/releases/latest", GHRepo))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer util.Close(resp)
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK:
|
||||
if resp.Body != nil {
|
||||
var result GitHubRelease
|
||||
err := json.NewDecoder(resp.Body).Decode(&result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return createReleaseInfo(result)
|
||||
}
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("server replied with HTTP %v", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func createReleaseInfo(ghResp GitHubRelease) (*DataResponse, error) {
|
||||
if ghResp.TagName == "" {
|
||||
return nil, errors.New("server response does not contain tag_name")
|
||||
}
|
||||
|
||||
sv, err := semver.Parse(version.Version)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse current version %q: %w", version.Version, err)
|
||||
}
|
||||
|
||||
latestSV, err := semver.Parse(ghResp.TagName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse latest version %q: %w", ghResp.TagName, err)
|
||||
}
|
||||
|
||||
isLatest := sv.Compare(latestSV) >= 0
|
||||
|
||||
// Note: alternatively, we could look through the assets in the GH API response to find a matching asset,
|
||||
// and use its URL. However, this is not guaranteed to be more robust, and wouldn't use the 'openpolicyagent.org' domain.
|
||||
downloadLink := fmt.Sprintf("https://openpolicyagent.org/downloads/%v/opa_%v_%v",
|
||||
ghResp.TagName, runtime.GOOS, runtime.GOARCH)
|
||||
|
||||
if runtime.GOARCH == "arm64" {
|
||||
downloadLink = fmt.Sprintf("%v_static", downloadLink)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(runtime.GOOS, "win") {
|
||||
downloadLink = fmt.Sprintf("%v.exe", downloadLink)
|
||||
}
|
||||
|
||||
return &DataResponse{
|
||||
Latest: ReleaseDetails{
|
||||
Download: downloadLink,
|
||||
ReleaseNotes: ghResp.ReleaseNotes,
|
||||
LatestRelease: ghResp.TagName,
|
||||
OPAUpToDate: isLatest,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (*GitHubVersionChecker) RegisterGatherer(_ string, _ Gatherer) {
|
||||
// no-op for this implementation
|
||||
}
|
||||
|
||||
// IsSet returns true if dr is populated.
|
||||
func (dr *DataResponse) IsSet() bool {
|
||||
return dr != nil && dr.Latest.LatestRelease != "" && dr.Latest.Download != "" && dr.Latest.ReleaseNotes != ""
|
||||
}
|
||||
|
||||
// Slice returns the dr as a slice of key-value string pairs. If dr is nil, this function returns an empty slice.
|
||||
func (dr *DataResponse) Slice() [][2]string {
|
||||
|
||||
if !dr.IsSet() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return [][2]string{
|
||||
{"Latest Upstream Version", strings.TrimPrefix(dr.Latest.LatestRelease, "v")},
|
||||
{"Download", dr.Latest.Download},
|
||||
{"Release Notes", dr.Latest.ReleaseNotes},
|
||||
}
|
||||
}
|
||||
|
||||
// Pretty returns OPA release information in a human-readable format.
|
||||
func (dr *DataResponse) Pretty() string {
|
||||
if !dr.IsSet() {
|
||||
return ""
|
||||
}
|
||||
|
||||
pairs := dr.Slice()
|
||||
lines := make([]string, 0, len(pairs))
|
||||
|
||||
for _, pair := range pairs {
|
||||
lines = append(lines, fmt.Sprintf("%v: %v", pair[0], pair[1]))
|
||||
}
|
||||
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
+4
-3
@@ -1675,7 +1675,7 @@ var JSONPatch = &Builtin{
|
||||
"Additionally works on sets, where a value contained in the set is considered to be its path.",
|
||||
Decl: types.NewFunction(
|
||||
types.Args(
|
||||
types.Named("object", types.A).Description("the object to patch"), // TODO(sr): types.A?
|
||||
types.Named("target", types.A).Description("the object, array or set to patch"),
|
||||
types.Named("patches", types.NewArray(
|
||||
nil,
|
||||
types.NewObject(
|
||||
@@ -3404,8 +3404,9 @@ var InternalTestCase = &Builtin{
|
||||
}
|
||||
|
||||
var InternalTemplateString = &Builtin{
|
||||
Name: "internal.template_string",
|
||||
Decl: types.NewFunction([]types.Type{types.NewArray(nil, types.A)}, types.S),
|
||||
Name: "internal.template_string",
|
||||
Decl: types.NewFunction([]types.Type{types.NewArray(nil, types.A)}, types.S),
|
||||
CanSkipBctx: true, // Uses bctx.Location for error reporting, but that is always provided in eval
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+539
-272
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -63,9 +63,9 @@ func checkDocumentConflicts(node *TreeNode, exists func([]string) (bool, error),
|
||||
if len(node.Values) > 0 {
|
||||
s := strings.Join(path, "/")
|
||||
if ok, err := exists(path); err != nil {
|
||||
return Errors{NewError(CompileErr, node.Values[0].(*Rule).Loc(), "conflict check for data path %v: %v", s, err.Error())}
|
||||
return Errors{NewError(CompileErr, node.Values[0].Loc(), "conflict check for data path %v: %v", s, err.Error())}
|
||||
} else if ok {
|
||||
return Errors{NewError(CompileErr, node.Values[0].(*Rule).Loc(), "conflicting rule for data path %v found", s)}
|
||||
return Errors{NewError(CompileErr, node.Values[0].Loc(), "conflicting rule for data path %v found", s)}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+28
-39
@@ -586,28 +586,24 @@ func newTrieNodeImpl() *trieNode {
|
||||
}
|
||||
|
||||
func (node *trieNode) Do(walker trieWalker) {
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
next := walker.Do(node)
|
||||
if next == nil {
|
||||
return
|
||||
}
|
||||
if node.any != nil {
|
||||
node.any.Do(next)
|
||||
}
|
||||
if node.undefined != nil {
|
||||
node.undefined.Do(next)
|
||||
}
|
||||
|
||||
node.any.Do(next)
|
||||
node.undefined.Do(next)
|
||||
|
||||
node.scalars.Iter(func(_ Value, child *trieNode) bool {
|
||||
child.Do(next)
|
||||
return false
|
||||
})
|
||||
|
||||
if node.array != nil {
|
||||
node.array.Do(next)
|
||||
}
|
||||
if node.next != nil {
|
||||
node.next.Do(next)
|
||||
}
|
||||
node.array.Do(next)
|
||||
node.next.Do(next)
|
||||
}
|
||||
|
||||
func (node *trieNode) Insert(ref Ref, value Value, mapper *valueMapper) *trieNode {
|
||||
@@ -699,7 +695,6 @@ func (node *trieNode) insertArray(arr *Array) *trieNode {
|
||||
}
|
||||
|
||||
func (node *trieNode) traverse(resolver ValueResolver, tr *trieTraversalResult) error {
|
||||
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -712,31 +707,31 @@ func (node *trieNode) traverse(resolver ValueResolver, tr *trieTraversalResult)
|
||||
return err
|
||||
}
|
||||
|
||||
if node.undefined != nil {
|
||||
err = node.undefined.Traverse(resolver, tr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = node.undefined.Traverse(resolver, tr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if node.any != nil {
|
||||
err = node.any.Traverse(resolver, tr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = node.any.Traverse(resolver, tr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := node.traverseValue(resolver, tr, v); err != nil {
|
||||
err = node.traverseValue(resolver, tr, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range node.mappers {
|
||||
if err := node.traverseValue(resolver, tr, node.mappers[i].MapValue(v)); err != nil {
|
||||
return err
|
||||
mapped := node.mappers[i].MapValue(v)
|
||||
if !ValueEqual(mapped, v) {
|
||||
if err := node.traverseValue(resolver, tr, mapped); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -747,9 +742,6 @@ func (node *trieNode) traverseValue(resolver ValueResolver, tr *trieTraversalRes
|
||||
|
||||
switch value := value.(type) {
|
||||
case *Array:
|
||||
if node.array == nil {
|
||||
return nil
|
||||
}
|
||||
return node.array.traverseArray(resolver, tr, value)
|
||||
|
||||
case Null, Boolean, Number, String:
|
||||
@@ -764,16 +756,17 @@ func (node *trieNode) traverseValue(resolver ValueResolver, tr *trieTraversalRes
|
||||
}
|
||||
|
||||
func (node *trieNode) traverseArray(resolver ValueResolver, tr *trieTraversalResult, arr *Array) error {
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if arr.Len() == 0 {
|
||||
return node.Traverse(resolver, tr)
|
||||
}
|
||||
|
||||
if node.any != nil {
|
||||
err := node.any.traverseArray(resolver, tr, arr.Slice(1, -1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err := node.any.traverseArray(resolver, tr, arr.Slice(1, -1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
head := arr.Elem(0).Value
|
||||
@@ -784,10 +777,7 @@ func (node *trieNode) traverseArray(resolver ValueResolver, tr *trieTraversalRes
|
||||
|
||||
switch head := head.(type) {
|
||||
case Null, Boolean, Number, String:
|
||||
child, ok := node.scalars.Get(head)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
child, _ := node.scalars.Get(head)
|
||||
return child.traverseArray(resolver, tr, arr.Slice(1, -1))
|
||||
}
|
||||
|
||||
@@ -795,7 +785,6 @@ func (node *trieNode) traverseArray(resolver ValueResolver, tr *trieTraversalRes
|
||||
}
|
||||
|
||||
func (node *trieNode) traverseUnknown(resolver ValueResolver, tr *trieTraversalResult) error {
|
||||
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
+31
-14
@@ -5,6 +5,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"iter"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
@@ -19,23 +20,26 @@ type internable interface {
|
||||
|
||||
var (
|
||||
InternedNullValue Value = Null{}
|
||||
InternedNullTerm = &Term{Value: InternedNullValue}
|
||||
InternedNullTerm = NewTerm(InternedNullValue)
|
||||
|
||||
InternedBooleanTrueValue Value = Boolean(true)
|
||||
InternedBooleanFalseValue Value = Boolean(false)
|
||||
InternedBooleanTrueTerm = &Term{Value: InternedBooleanTrueValue}
|
||||
InternedBooleanFalseTerm = &Term{Value: InternedBooleanFalseValue}
|
||||
InternedEmptyStringValue Value = String("")
|
||||
InternedEmptyArrayValue Value = NewArray()
|
||||
InternedEmptyRefValue Value = Ref{}
|
||||
InternedEmptyObjectValue Value = NewObject()
|
||||
InternedEmptySetValue Value = NewSet()
|
||||
|
||||
InternedEmptyString = StringTerm("")
|
||||
InternedEmptyObject = ObjectTerm()
|
||||
InternedEmptyArray = NewTerm(InternedEmptyArrayValue)
|
||||
InternedEmptySet = SetTerm()
|
||||
|
||||
InternedEmptyArrayValue = NewArray()
|
||||
InternedBooleanTrue = NewTerm(InternedBooleanTrueValue)
|
||||
InternedBooleanFalse = NewTerm(InternedBooleanFalseValue)
|
||||
InternedEmptyString = NewTerm(InternedEmptyStringValue)
|
||||
InternedEmptyObject = NewTerm(InternedEmptyObjectValue)
|
||||
InternedEmptyArray = NewTerm(InternedEmptyArrayValue)
|
||||
InternedEmptySet = NewTerm(InternedEmptySetValue)
|
||||
|
||||
// since this is by far the most common negative number
|
||||
minusOneValue Value = Number("-1")
|
||||
minusOneTerm = &Term{Value: minusOneValue}
|
||||
minusOneTerm = NewTerm(minusOneValue)
|
||||
|
||||
internedStringTerms = map[string]*Term{
|
||||
"": InternedEmptyString,
|
||||
@@ -68,7 +72,7 @@ func InternStringTerm(str ...string) {
|
||||
continue
|
||||
}
|
||||
|
||||
internedStringTerms[s] = StringTerm(s)
|
||||
internedStringTerms[s] = &Term{Value: String(s)}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,6 +219,19 @@ func InternedIntNumberTermFromString(s string) *Term {
|
||||
return nil
|
||||
}
|
||||
|
||||
// InternedIntRange returns a sequence of interned integer number terms
|
||||
// from start (inclusive) to end (exclusive). For values outside of the
|
||||
// interned range, non-interned IntNumberTerms are returned.
|
||||
func InternedIntRange(start, end int) iter.Seq[*Term] {
|
||||
return func(yield func(*Term) bool) {
|
||||
for i := start; i < end; i++ {
|
||||
if !yield(internedIntNumberTerm(i)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HasInternedIntNumberTerm returns true if the given integer value maps to an interned
|
||||
// term, otherwise false.
|
||||
func HasInternedIntNumberTerm(i int) bool {
|
||||
@@ -253,10 +270,10 @@ func internedBooleanValue(b bool) Value {
|
||||
// InternedBooleanTerm returns an interned term with the given boolean value.
|
||||
func internedBooleanTerm(b bool) *Term {
|
||||
if b {
|
||||
return InternedBooleanTrueTerm
|
||||
return InternedBooleanTrue
|
||||
}
|
||||
|
||||
return InternedBooleanFalseTerm
|
||||
return InternedBooleanFalse
|
||||
}
|
||||
|
||||
func internedIntNumberValue(i int) Value {
|
||||
@@ -323,7 +340,7 @@ func init() {
|
||||
// Various
|
||||
"data", "input", "result", "keywords", "path", "v1", "error", "partial",
|
||||
// HTTP
|
||||
"code", "message", "status_code", "method", "url", "uri",
|
||||
"code", "message", "status_code", "method", "url", "uri", "body", "raw_body", "headers", "query_params",
|
||||
// JWT
|
||||
"enc", "cty", "iss", "exp", "nbf", "aud", "secret", "cert",
|
||||
// Decisions
|
||||
|
||||
+1
-1
@@ -1893,7 +1893,7 @@ func (p *Parser) parseNumber() *Term {
|
||||
func (p *Parser) parseString() *Term {
|
||||
if p.s.lit[0] == '"' {
|
||||
if p.s.lit == "\"\"" {
|
||||
return NewTerm(InternedEmptyString.Value).SetLocation(p.s.Loc())
|
||||
return NewTerm(InternedEmptyStringValue).SetLocation(p.s.Loc())
|
||||
}
|
||||
|
||||
inner := p.s.lit[1 : len(p.s.lit)-1]
|
||||
|
||||
+1
-1
@@ -989,7 +989,7 @@ func (head *Head) HasDynamicRef() bool {
|
||||
|
||||
// Copy returns a deep copy of a.
|
||||
func (a Args) Copy() Args {
|
||||
cpy := Args{}
|
||||
cpy := make(Args, 0, len(a))
|
||||
for _, t := range a {
|
||||
cpy = append(cpy, t.Copy())
|
||||
}
|
||||
|
||||
-9
@@ -68,15 +68,6 @@ func (pkg *Package) AppendText(buf []byte) ([]byte, error) {
|
||||
|
||||
path := pkg.Path[1:] // omit "data"
|
||||
|
||||
if s, ok := path[0].Value.(String); ok {
|
||||
buf = append(buf, s...) // first term should never be quoted
|
||||
if len(path) == 1 {
|
||||
return buf, nil
|
||||
}
|
||||
buf = append(buf, '.')
|
||||
path = path[1:]
|
||||
}
|
||||
|
||||
return path.AppendText(buf)
|
||||
}
|
||||
|
||||
|
||||
+4
@@ -99,6 +99,10 @@ func (o *object) StringLength() (n int) {
|
||||
return n // surrounding {} but also minus last ", "
|
||||
}
|
||||
|
||||
func (l *lazyObj) StringLength() int {
|
||||
return l.force().(*object).StringLength()
|
||||
}
|
||||
|
||||
func (ts *TemplateString) StringLength() (n int) {
|
||||
for _, p := range ts.Parts {
|
||||
switch x := p.(type) {
|
||||
|
||||
+15
-1
@@ -24,6 +24,17 @@ import (
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// maxBindingsEstimate is the cap for binding count estimates in comprehensions.
|
||||
// This value aligns with maxLinearScan in topdown/bindings.go.
|
||||
const maxBindingsEstimate = 16
|
||||
|
||||
// EstimateBodyBindingCount returns an estimate of the number of bindings needed
|
||||
// for evaluating a comprehension body. It uses the body length as a heuristic,
|
||||
// capped at maxBindingsEstimate.
|
||||
func EstimateBodyBindingCount(body Body) (estimate int) {
|
||||
return min(len(body), maxBindingsEstimate)
|
||||
}
|
||||
|
||||
var (
|
||||
NullValue Value = Null{}
|
||||
|
||||
@@ -53,6 +64,8 @@ type Value interface {
|
||||
Hash() int // Returns hash code of the value.
|
||||
IsGround() bool // IsGround returns true if this value is not a variable or contains no variables.
|
||||
String() string // String returns a human readable string representation of the value.
|
||||
|
||||
StringLengther // All Values must be able to report their string length during optimization.
|
||||
}
|
||||
|
||||
// InterfaceToValue converts a native Go value x to a Value.
|
||||
@@ -669,8 +682,9 @@ func NumberTerm(n json.Number) *Term {
|
||||
}
|
||||
|
||||
// IntNumberTerm creates a new Term with an integer Number value.
|
||||
// For values between -1 and 512, returns a cached Term to reduce allocations.
|
||||
func IntNumberTerm(i int) *Term {
|
||||
return &Term{Value: newIntNumberValue(i)}
|
||||
return internedIntNumberTerm(i)
|
||||
}
|
||||
|
||||
// UIntNumberTerm creates a new Term with an unsigned integer Number value.
|
||||
|
||||
+12
-2
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
// VarSet represents a set of variables.
|
||||
type VarSet map[Var]struct{}
|
||||
type VarSet map[Var]struct{ *Location }
|
||||
|
||||
// NewVarSet returns a new VarSet containing the specified variables.
|
||||
func NewVarSet(vs ...Var) VarSet {
|
||||
@@ -30,7 +30,16 @@ func NewVarSetOfSize(size int) VarSet {
|
||||
|
||||
// Add updates the set to include the variable "v".
|
||||
func (s VarSet) Add(v Var) {
|
||||
s[v] = struct{}{}
|
||||
if _, ok := s[v]; !ok {
|
||||
s[v] = struct{ *Location }{}
|
||||
}
|
||||
}
|
||||
|
||||
func (s VarSet) AddLocation(v Var, l *Location) {
|
||||
if entry, ok := s[v]; ok {
|
||||
entry.Location = l
|
||||
s[v] = entry
|
||||
}
|
||||
}
|
||||
|
||||
// Contains returns true if the set contains the variable "v".
|
||||
@@ -54,6 +63,7 @@ func (s VarSet) Diff(vs VarSet) VarSet {
|
||||
for v := range s {
|
||||
if !vs.Contains(v) {
|
||||
r.Add(v)
|
||||
r.AddLocation(v, s[v].Location)
|
||||
}
|
||||
}
|
||||
return r
|
||||
|
||||
+19
-6
@@ -41,12 +41,13 @@ type (
|
||||
|
||||
// VarVisitorParams contains settings for a VarVisitor.
|
||||
VarVisitorParams struct {
|
||||
SkipRefHead bool
|
||||
SkipRefCallHead bool
|
||||
SkipObjectKeys bool
|
||||
SkipClosures bool
|
||||
SkipWithTarget bool
|
||||
SkipSets bool
|
||||
SkipRefHead bool
|
||||
SkipRefCallHead bool
|
||||
SkipObjectKeys bool
|
||||
SkipClosures bool
|
||||
SkipWithTarget bool
|
||||
SkipSets bool
|
||||
SkipTemplateStrings bool
|
||||
}
|
||||
|
||||
// Visitor defines the interface for iterating AST elements. The Visit function
|
||||
@@ -870,8 +871,14 @@ func (vis *VarVisitor) visit(v any) bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if vis.params.SkipTemplateStrings {
|
||||
if _, ok := v.(*TemplateString); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if v, ok := v.(Var); ok {
|
||||
vis.Add(v)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -935,6 +942,9 @@ func (vis *VarVisitor) Walk(x any) {
|
||||
vis.Walk(x.Value.Value)
|
||||
case *Term:
|
||||
vis.Walk(x.Value)
|
||||
if vVar, ok := x.Value.(Var); ok {
|
||||
vis.vars.AddLocation(vVar, x.Location)
|
||||
}
|
||||
case Ref:
|
||||
for i := range x {
|
||||
vis.Walk(x[i].Value)
|
||||
@@ -1001,6 +1011,9 @@ func (vis *VarVisitor) WalkRef(ref Ref) {
|
||||
}
|
||||
for _, term := range ref {
|
||||
vis.Walk(term.Value)
|
||||
if vVar, ok := term.Value.(Var); ok {
|
||||
vis.vars.AddLocation(vVar, term.Location)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-3
@@ -1,6 +1,3 @@
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
package bundle
|
||||
|
||||
import (
|
||||
|
||||
-4
@@ -144,10 +144,6 @@ func verifyJWTSignature(token string, bvc *VerificationConfig) (*DecodedSignatur
|
||||
|
||||
// Because we want to fallback to ds.KeyID when we can't find the
|
||||
// keyID, we need to parse the payload here already.
|
||||
//
|
||||
// (lestrrat) Whoa, you're going to trust the payload before you
|
||||
// verify the signature? Even if it's for backwrds compatibility,
|
||||
// Is this OK?
|
||||
decoder := base64.RawURLEncoding
|
||||
payload := make([]byte, decoder.DecodedLen(len(payloadb64)))
|
||||
if _, err := decoder.Decode(payload, payloadb64); err != nil {
|
||||
|
||||
-3
@@ -2,9 +2,6 @@
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
package capabilities
|
||||
|
||||
import (
|
||||
|
||||
-393
@@ -1,393 +0,0 @@
|
||||
// Copyright 2018 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 config implements OPA configuration file parsing and validation.
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/open-policy-agent/opa/internal/ref"
|
||||
"github.com/open-policy-agent/opa/v1/ast"
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
"github.com/open-policy-agent/opa/v1/version"
|
||||
)
|
||||
|
||||
// ServerConfig represents the different server configuration options.
|
||||
type ServerConfig struct {
|
||||
Metrics json.RawMessage `json:"metrics,omitempty"`
|
||||
|
||||
Encoding json.RawMessage `json:"encoding,omitempty"`
|
||||
Decoding json.RawMessage `json:"decoding,omitempty"`
|
||||
}
|
||||
|
||||
// Clone creates a deep copy of ServerConfig.
|
||||
func (s *ServerConfig) Clone() *ServerConfig {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
clone := &ServerConfig{}
|
||||
|
||||
if s.Encoding != nil {
|
||||
clone.Encoding = make(json.RawMessage, len(s.Encoding))
|
||||
copy(clone.Encoding, s.Encoding)
|
||||
}
|
||||
if s.Decoding != nil {
|
||||
clone.Decoding = make(json.RawMessage, len(s.Decoding))
|
||||
copy(clone.Decoding, s.Decoding)
|
||||
}
|
||||
if s.Metrics != nil {
|
||||
clone.Metrics = make(json.RawMessage, len(s.Metrics))
|
||||
copy(clone.Metrics, s.Metrics)
|
||||
}
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
// StorageConfig represents Config's storage options.
|
||||
type StorageConfig struct {
|
||||
Disk json.RawMessage `json:"disk,omitempty"`
|
||||
}
|
||||
|
||||
// Clone creates a deep copy of StorageConfig.
|
||||
func (s *StorageConfig) Clone() *StorageConfig {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
clone := &StorageConfig{}
|
||||
|
||||
if s.Disk != nil {
|
||||
clone.Disk = make(json.RawMessage, len(s.Disk))
|
||||
copy(clone.Disk, s.Disk)
|
||||
}
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
// Config represents the configuration file that OPA can be started with.
|
||||
type Config struct {
|
||||
Services json.RawMessage `json:"services,omitempty"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
Discovery json.RawMessage `json:"discovery,omitempty"`
|
||||
Bundle json.RawMessage `json:"bundle,omitempty"` // Deprecated: Use `bundles` instead
|
||||
Bundles json.RawMessage `json:"bundles,omitempty"`
|
||||
DecisionLogs json.RawMessage `json:"decision_logs,omitempty"`
|
||||
Status json.RawMessage `json:"status,omitempty"`
|
||||
Plugins map[string]json.RawMessage `json:"plugins,omitempty"`
|
||||
Keys json.RawMessage `json:"keys,omitempty"`
|
||||
DefaultDecision *string `json:"default_decision,omitempty"`
|
||||
DefaultAuthorizationDecision *string `json:"default_authorization_decision,omitempty"`
|
||||
Caching json.RawMessage `json:"caching,omitempty"`
|
||||
NDBuiltinCache bool `json:"nd_builtin_cache,omitempty"`
|
||||
PersistenceDirectory *string `json:"persistence_directory,omitempty"`
|
||||
DistributedTracing json.RawMessage `json:"distributed_tracing,omitempty"`
|
||||
Server *ServerConfig `json:"server,omitempty"`
|
||||
Storage *StorageConfig `json:"storage,omitempty"`
|
||||
Extra map[string]json.RawMessage `json:"-"`
|
||||
}
|
||||
|
||||
// ParseConfig returns a valid Config object with defaults injected. The id
|
||||
// and version parameters will be set in the labels map.
|
||||
func ParseConfig(raw []byte, id string) (*Config, error) {
|
||||
// NOTE(sr): based on https://stackoverflow.com/a/33499066/993018
|
||||
var result Config
|
||||
objValue := reflect.ValueOf(&result).Elem()
|
||||
knownFields := map[string]reflect.Value{}
|
||||
for i := 0; i != objValue.NumField(); i++ {
|
||||
jsonName := strings.Split(objValue.Type().Field(i).Tag.Get("json"), ",")[0]
|
||||
knownFields[jsonName] = objValue.Field(i)
|
||||
}
|
||||
|
||||
if err := util.Unmarshal(raw, &result.Extra); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for key, chunk := range result.Extra {
|
||||
if field, found := knownFields[key]; found {
|
||||
if err := util.Unmarshal(chunk, field.Addr().Interface()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
delete(result.Extra, key)
|
||||
}
|
||||
}
|
||||
if len(result.Extra) == 0 {
|
||||
result.Extra = nil
|
||||
}
|
||||
return &result, result.validateAndInjectDefaults(id)
|
||||
}
|
||||
|
||||
// PluginNames returns a sorted list of names of enabled plugins.
|
||||
func (c Config) PluginNames() (result []string) {
|
||||
if c.Bundle != nil || c.Bundles != nil {
|
||||
result = append(result, "bundles")
|
||||
}
|
||||
if c.Status != nil {
|
||||
result = append(result, "status")
|
||||
}
|
||||
if c.DecisionLogs != nil {
|
||||
result = append(result, "decision_logs")
|
||||
}
|
||||
for name := range c.Plugins {
|
||||
result = append(result, name)
|
||||
}
|
||||
sort.Strings(result)
|
||||
return result
|
||||
}
|
||||
|
||||
// PluginsEnabled returns true if one or more plugin features are enabled.
|
||||
//
|
||||
// Deprecated: Use PluginNames instead.
|
||||
func (c Config) PluginsEnabled() bool {
|
||||
return c.Bundle != nil || c.Bundles != nil || c.DecisionLogs != nil || c.Status != nil || len(c.Plugins) > 0
|
||||
}
|
||||
|
||||
// DefaultDecisionRef returns the default decision as a reference.
|
||||
func (c Config) DefaultDecisionRef() ast.Ref {
|
||||
r, _ := ref.ParseDataPath(*c.DefaultDecision)
|
||||
return r
|
||||
}
|
||||
|
||||
// DefaultAuthorizationDecisionRef returns the default authorization decision
|
||||
// as a reference.
|
||||
func (c Config) DefaultAuthorizationDecisionRef() ast.Ref {
|
||||
r, _ := ref.ParseDataPath(*c.DefaultAuthorizationDecision)
|
||||
return r
|
||||
}
|
||||
|
||||
// NDBuiltinCacheEnabled returns if the ND builtins cache should be used.
|
||||
func (c Config) NDBuiltinCacheEnabled() bool {
|
||||
return c.NDBuiltinCache
|
||||
}
|
||||
|
||||
// GetPersistenceDirectory returns the configured persistence directory, or $PWD/.opa if none is configured
|
||||
func (c Config) GetPersistenceDirectory() (string, error) {
|
||||
if c.PersistenceDirectory == nil {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(pwd, ".opa"), nil
|
||||
}
|
||||
return *c.PersistenceDirectory, nil
|
||||
}
|
||||
|
||||
// ActiveConfig returns OPA's active configuration
|
||||
// with the credentials and crypto keys removed
|
||||
func (c *Config) ActiveConfig() (any, error) {
|
||||
bs, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result map[string]any
|
||||
if err := util.UnmarshalJSON(bs, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, e := range c.Extra {
|
||||
var v any
|
||||
if err := util.UnmarshalJSON(e, &v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[k] = v
|
||||
}
|
||||
|
||||
if err := removeServiceCredentials(result["services"]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := removeCryptoKeys(result["keys"]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Clone creates a deep copy of the Config struct
|
||||
func (c *Config) Clone() *Config {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
clone := &Config{
|
||||
NDBuiltinCache: c.NDBuiltinCache,
|
||||
Server: c.Server.Clone(),
|
||||
Storage: c.Storage.Clone(),
|
||||
Labels: maps.Clone(c.Labels),
|
||||
}
|
||||
|
||||
if c.Services != nil {
|
||||
clone.Services = make(json.RawMessage, len(c.Services))
|
||||
copy(clone.Services, c.Services)
|
||||
}
|
||||
if c.Discovery != nil {
|
||||
clone.Discovery = make(json.RawMessage, len(c.Discovery))
|
||||
copy(clone.Discovery, c.Discovery)
|
||||
}
|
||||
if c.Bundle != nil {
|
||||
clone.Bundle = make(json.RawMessage, len(c.Bundle))
|
||||
copy(clone.Bundle, c.Bundle)
|
||||
}
|
||||
if c.Bundles != nil {
|
||||
clone.Bundles = make(json.RawMessage, len(c.Bundles))
|
||||
copy(clone.Bundles, c.Bundles)
|
||||
}
|
||||
if c.DecisionLogs != nil {
|
||||
clone.DecisionLogs = make(json.RawMessage, len(c.DecisionLogs))
|
||||
copy(clone.DecisionLogs, c.DecisionLogs)
|
||||
}
|
||||
if c.Status != nil {
|
||||
clone.Status = make(json.RawMessage, len(c.Status))
|
||||
copy(clone.Status, c.Status)
|
||||
}
|
||||
if c.Keys != nil {
|
||||
clone.Keys = make(json.RawMessage, len(c.Keys))
|
||||
copy(clone.Keys, c.Keys)
|
||||
}
|
||||
if c.Caching != nil {
|
||||
clone.Caching = make(json.RawMessage, len(c.Caching))
|
||||
copy(clone.Caching, c.Caching)
|
||||
}
|
||||
if c.DistributedTracing != nil {
|
||||
clone.DistributedTracing = make(json.RawMessage, len(c.DistributedTracing))
|
||||
copy(clone.DistributedTracing, c.DistributedTracing)
|
||||
}
|
||||
|
||||
if c.DefaultDecision != nil {
|
||||
s := *c.DefaultDecision
|
||||
clone.DefaultDecision = &s
|
||||
}
|
||||
if c.DefaultAuthorizationDecision != nil {
|
||||
s := *c.DefaultAuthorizationDecision
|
||||
clone.DefaultAuthorizationDecision = &s
|
||||
}
|
||||
if c.PersistenceDirectory != nil {
|
||||
s := *c.PersistenceDirectory
|
||||
clone.PersistenceDirectory = &s
|
||||
}
|
||||
|
||||
if c.Plugins != nil {
|
||||
clone.Plugins = make(map[string]json.RawMessage, len(c.Plugins))
|
||||
for k, v := range c.Plugins {
|
||||
if v != nil {
|
||||
clone.Plugins[k] = make(json.RawMessage, len(v))
|
||||
copy(clone.Plugins[k], v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if c.Extra != nil {
|
||||
clone.Extra = make(map[string]json.RawMessage, len(c.Extra))
|
||||
for k, v := range c.Extra {
|
||||
if v != nil {
|
||||
clone.Extra[k] = make(json.RawMessage, len(v))
|
||||
copy(clone.Extra[k], v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
func (c *Config) validateAndInjectDefaults(id string) error {
|
||||
if c.DefaultDecision == nil {
|
||||
s := defaultDecisionPath
|
||||
c.DefaultDecision = &s
|
||||
}
|
||||
|
||||
_, err := ref.ParseDataPath(*c.DefaultDecision)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.DefaultAuthorizationDecision == nil {
|
||||
s := defaultAuthorizationDecisionPath
|
||||
c.DefaultAuthorizationDecision = &s
|
||||
}
|
||||
|
||||
_, err = ref.ParseDataPath(*c.DefaultAuthorizationDecision)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Labels == nil {
|
||||
c.Labels = map[string]string{}
|
||||
}
|
||||
|
||||
c.Labels["id"] = id
|
||||
c.Labels["version"] = version.Version
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeServiceCredentials(x any) error {
|
||||
switch x := x.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
case []any:
|
||||
for _, v := range x {
|
||||
err := removeKey(v, "credentials")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case map[string]any:
|
||||
for _, v := range x {
|
||||
err := removeKey(v, "credentials")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("illegal service config type: %T", x)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeCryptoKeys(x any) error {
|
||||
switch x := x.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
case map[string]any:
|
||||
for _, v := range x {
|
||||
err := removeKey(v, "key", "private_key")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("illegal keys config type: %T", x)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeKey(x any, keys ...string) error {
|
||||
val, ok := x.(map[string]any)
|
||||
if !ok {
|
||||
return errors.New("type assertion error")
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
delete(val, key)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
defaultDecisionPath = "/system/main"
|
||||
defaultAuthorizationDecisionPath = "/system/authz/allow"
|
||||
)
|
||||
-97
@@ -1,97 +0,0 @@
|
||||
// Copyright 2023 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 hooks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/config"
|
||||
topdown_cache "github.com/open-policy-agent/opa/v1/topdown/cache"
|
||||
)
|
||||
|
||||
// Hook is a hook to be called in some select places in OPA's operation.
|
||||
//
|
||||
// The base Hook interface is any, and wherever a hook can occur, the calling code
|
||||
// will check if your hook implements an appropriate interface. If so, your hook
|
||||
// is called.
|
||||
//
|
||||
// This allows you to only hook in to behavior you care about, and it allows the
|
||||
// OPA to add more hooks in the future.
|
||||
//
|
||||
// All hook interfaces in this package have Hook in the name. Hooks must be safe
|
||||
// for concurrent use. It is expected that hooks are fast; if a hook needs to take
|
||||
// time, then copy what you need and ensure the hook is async.
|
||||
//
|
||||
// When multiple instances of a hook are provided, they are all going to be executed
|
||||
// in an unspecified order (it's a map-range call underneath). If you need hooks to
|
||||
// be run in order, you can wrap them into another hook, and configure that one.
|
||||
type Hook any
|
||||
|
||||
// Hooks is the type used for every struct in OPA that can work with hooks.
|
||||
type Hooks struct {
|
||||
m map[Hook]struct{} // we are NOT providing a stable invocation ordering
|
||||
}
|
||||
|
||||
// New creates a new instance of Hooks.
|
||||
func New(hs ...Hook) Hooks {
|
||||
h := Hooks{m: make(map[Hook]struct{}, len(hs))}
|
||||
for i := range hs {
|
||||
h.m[hs[i]] = struct{}{}
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (hs Hooks) Each(fn func(Hook)) {
|
||||
for h := range hs.m {
|
||||
fn(h)
|
||||
}
|
||||
}
|
||||
|
||||
func (hs Hooks) Len() int {
|
||||
return len(hs.m)
|
||||
}
|
||||
|
||||
// ConfigHook allows inspecting or rewriting the configuration when the plugin
|
||||
// manager is processing it.
|
||||
// Note that this hook is not run when the plugin manager is reconfigured. This
|
||||
// usually only happens when there's a new config from a discovery bundle, and
|
||||
// for processing _that_, there's `ConfigDiscoveryHook`.
|
||||
type ConfigHook interface {
|
||||
OnConfig(context.Context, *config.Config) (*config.Config, error)
|
||||
}
|
||||
|
||||
// ConfigHook allows inspecting or rewriting the discovered configuration when
|
||||
// the discovery plugin is processing it.
|
||||
type ConfigDiscoveryHook interface {
|
||||
OnConfigDiscovery(context.Context, *config.Config) (*config.Config, error)
|
||||
}
|
||||
|
||||
// InterQueryCacheHook allows access to the server's inter-query cache instance.
|
||||
// It's useful for out-of-tree handlers that also need to evaluate something.
|
||||
// Using this hook, they can share the caches with the rest of OPA.
|
||||
type InterQueryCacheHook interface {
|
||||
OnInterQueryCache(context.Context, topdown_cache.InterQueryCache) error
|
||||
}
|
||||
|
||||
// InterQueryValueCacheHook allows access to the server's inter-query value cache
|
||||
// instance.
|
||||
type InterQueryValueCacheHook interface {
|
||||
OnInterQueryValueCache(context.Context, topdown_cache.InterQueryValueCache) error
|
||||
}
|
||||
|
||||
func (hs Hooks) Validate() error {
|
||||
for h := range hs.m {
|
||||
switch h.(type) {
|
||||
case InterQueryCacheHook,
|
||||
InterQueryValueCacheHook,
|
||||
ConfigHook,
|
||||
ConfigDiscoveryHook: // OK
|
||||
default:
|
||||
return fmt.Errorf("unknown hook type %T", h)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
-1202
File diff suppressed because it is too large
Load Diff
-1210
File diff suppressed because it is too large
Load Diff
-1088
File diff suppressed because it is too large
Load Diff
-287
@@ -1,287 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
azureIMDSEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token"
|
||||
defaultAPIVersion = "2018-02-01"
|
||||
defaultResource = "https://storage.azure.com/"
|
||||
timeout = 5 * time.Second
|
||||
defaultAPIVersionForAppServiceMsi = "2019-08-01"
|
||||
defaultKeyVaultAPIVersion = "7.4"
|
||||
)
|
||||
|
||||
// azureManagedIdentitiesToken holds a token for managed identities for Azure resources
|
||||
type azureManagedIdentitiesToken struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn string `json:"expires_in"`
|
||||
ExpiresOn string `json:"expires_on"`
|
||||
NotBefore string `json:"not_before"`
|
||||
Resource string `json:"resource"`
|
||||
TokenType string `json:"token_type"`
|
||||
}
|
||||
|
||||
// azureManagedIdentitiesError represents an error fetching an azureManagedIdentitiesToken
|
||||
type azureManagedIdentitiesError struct {
|
||||
Err string `json:"error"`
|
||||
Description string `json:"error_description"`
|
||||
Endpoint string
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
func (e *azureManagedIdentitiesError) Error() string {
|
||||
return fmt.Sprintf("%v %s retrieving azure token from %s: %s", e.StatusCode, e.Err, e.Endpoint, e.Description)
|
||||
}
|
||||
|
||||
// azureManagedIdentitiesAuthPlugin uses an azureManagedIdentitiesToken.AccessToken for bearer authorization
|
||||
type azureManagedIdentitiesAuthPlugin struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
APIVersion string `json:"api_version"`
|
||||
Resource string `json:"resource"`
|
||||
ObjectID string `json:"object_id"`
|
||||
ClientID string `json:"client_id"`
|
||||
MiResID string `json:"mi_res_id"`
|
||||
UseAppServiceMsi bool `json:"use_app_service_msi,omitempty"`
|
||||
}
|
||||
|
||||
func (ap *azureManagedIdentitiesAuthPlugin) setDefaults() {
|
||||
if ap.Endpoint == "" {
|
||||
identityEndpoint := os.Getenv("IDENTITY_ENDPOINT")
|
||||
if identityEndpoint != "" {
|
||||
ap.UseAppServiceMsi = true
|
||||
ap.Endpoint = identityEndpoint
|
||||
} else {
|
||||
ap.Endpoint = azureIMDSEndpoint
|
||||
}
|
||||
}
|
||||
|
||||
if ap.Resource == "" {
|
||||
ap.Resource = defaultResource
|
||||
}
|
||||
|
||||
if ap.APIVersion == "" {
|
||||
if ap.UseAppServiceMsi {
|
||||
ap.APIVersion = defaultAPIVersionForAppServiceMsi
|
||||
} else {
|
||||
ap.APIVersion = defaultAPIVersion
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (ap *azureManagedIdentitiesAuthPlugin) NewClient(c Config) (*http.Client, error) {
|
||||
if c.Type == "oci" {
|
||||
return nil, errors.New("azure managed identities auth: OCI service not supported")
|
||||
}
|
||||
ap.setDefaults()
|
||||
t, err := DefaultTLSConfig(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return DefaultRoundTripperClient(t, *c.ResponseHeaderTimeoutSeconds), nil
|
||||
}
|
||||
|
||||
func (ap *azureManagedIdentitiesAuthPlugin) Prepare(req *http.Request) error {
|
||||
token, err := azureManagedIdentitiesTokenRequest(
|
||||
ap.Endpoint, ap.APIVersion, ap.Resource,
|
||||
ap.ObjectID, ap.ClientID, ap.MiResID,
|
||||
ap.UseAppServiceMsi,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", "Bearer "+token.AccessToken)
|
||||
return nil
|
||||
}
|
||||
|
||||
// azureManagedIdentitiesTokenRequest fetches an azureManagedIdentitiesToken
|
||||
func azureManagedIdentitiesTokenRequest(
|
||||
endpoint, apiVersion, resource, objectID, clientID, miResID string,
|
||||
useAppServiceMsi bool,
|
||||
) (azureManagedIdentitiesToken, error) {
|
||||
var token azureManagedIdentitiesToken
|
||||
e := buildAzureManagedIdentitiesRequestPath(endpoint, apiVersion, resource, objectID, clientID, miResID)
|
||||
|
||||
request, err := http.NewRequest("GET", e, nil)
|
||||
if err != nil {
|
||||
return token, err
|
||||
}
|
||||
if useAppServiceMsi {
|
||||
identityHeader := os.Getenv("IDENTITY_HEADER")
|
||||
if identityHeader == "" {
|
||||
return token, errors.New("azure managed identities auth: IDENTITY_HEADER env var not found")
|
||||
}
|
||||
request.Header.Add("x-identity-header", identityHeader)
|
||||
} else {
|
||||
request.Header.Add("Metadata", "true")
|
||||
}
|
||||
|
||||
httpClient := http.Client{Timeout: timeout}
|
||||
response, err := httpClient.Do(request)
|
||||
if err != nil {
|
||||
return token, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
data, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return token, err
|
||||
}
|
||||
|
||||
if s := response.StatusCode; s != http.StatusOK {
|
||||
var azureError azureManagedIdentitiesError
|
||||
err = json.Unmarshal(data, &azureError)
|
||||
if err != nil {
|
||||
return token, err
|
||||
}
|
||||
|
||||
azureError.Endpoint = e
|
||||
azureError.StatusCode = s
|
||||
return token, &azureError
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, &token)
|
||||
if err != nil {
|
||||
return token, err
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// buildAzureManagedIdentitiesRequestPath constructs the request URL for an Azure managed identities token request
|
||||
func buildAzureManagedIdentitiesRequestPath(
|
||||
endpoint, apiVersion, resource, objectID, clientID, miResID string,
|
||||
) string {
|
||||
params := url.Values{
|
||||
"api-version": []string{apiVersion},
|
||||
"resource": []string{resource},
|
||||
}
|
||||
|
||||
if objectID != "" {
|
||||
params.Add("object_id", objectID)
|
||||
}
|
||||
|
||||
if clientID != "" {
|
||||
params.Add("client_id", clientID)
|
||||
}
|
||||
|
||||
if miResID != "" {
|
||||
params.Add("mi_res_id", miResID)
|
||||
}
|
||||
|
||||
return endpoint + "?" + params.Encode()
|
||||
}
|
||||
|
||||
type azureKeyVaultSignPlugin struct {
|
||||
config azureKeyVaultConfig
|
||||
tokener func() (string, error)
|
||||
}
|
||||
|
||||
func newKeyVaultSignPlugin(ap *azureManagedIdentitiesAuthPlugin, cfg *azureKeyVaultConfig) *azureKeyVaultSignPlugin {
|
||||
resp := &azureKeyVaultSignPlugin{
|
||||
tokener: func() (string, error) {
|
||||
resp, err := azureManagedIdentitiesTokenRequest(
|
||||
ap.Endpoint,
|
||||
ap.APIVersion,
|
||||
cfg.URL.String(),
|
||||
ap.ObjectID,
|
||||
ap.ClientID,
|
||||
ap.MiResID,
|
||||
ap.UseAppServiceMsi)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.AccessToken, nil
|
||||
},
|
||||
config: *cfg,
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
func (akv *azureKeyVaultSignPlugin) setDefaults() {
|
||||
if akv.config.APIVersion == "" {
|
||||
akv.config.APIVersion = defaultKeyVaultAPIVersion
|
||||
}
|
||||
}
|
||||
|
||||
type kvRequest struct {
|
||||
Alg string `json:"alg"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type kvResponse struct {
|
||||
KID string `json:"kid"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// SignDigest() uses the Microsoft keyvault rest api to sign a byte digest
|
||||
// https://learn.microsoft.com/en-us/rest/api/keyvault/keys/sign/sign
|
||||
func (ap *azureKeyVaultSignPlugin) SignDigest(ctx context.Context, digest []byte) (string, error) {
|
||||
tkn, err := ap.tokener()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if ap.config.URL.Host == "" {
|
||||
return "", errors.New("keyvault host not set")
|
||||
}
|
||||
|
||||
signingURL := ap.config.URL.JoinPath("keys", ap.config.Key, ap.config.KeyVersion, "sign")
|
||||
q := signingURL.Query()
|
||||
q.Set("api-version", ap.config.APIVersion)
|
||||
signingURL.RawQuery = q.Encode()
|
||||
reqBody, err := json.Marshal(kvRequest{
|
||||
Alg: ap.config.Alg,
|
||||
Value: base64.StdEncoding.EncodeToString(digest)})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, signingURL.String(), bytes.NewBuffer(reqBody))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", "Bearer "+tkn)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
if resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
return "", fmt.Errorf("non 200 status code, got: %d. Body: %v", resp.StatusCode, string(b))
|
||||
}
|
||||
return "", fmt.Errorf("non 200 status code from keyvault sign, got: %d", resp.StatusCode)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", errors.New("failed to read keyvault response body")
|
||||
}
|
||||
|
||||
var res kvResponse
|
||||
err = json.Unmarshal(respBytes, &res)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("no valid keyvault response, got: %v", string(respBytes))
|
||||
}
|
||||
|
||||
return res.Value, nil
|
||||
}
|
||||
-173
@@ -1,173 +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 rest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultGCPMetadataEndpoint = "http://metadata.google.internal"
|
||||
defaultAccessTokenPath = "/computeMetadata/v1/instance/service-accounts/default/token"
|
||||
defaultIdentityTokenPath = "/computeMetadata/v1/instance/service-accounts/default/identity"
|
||||
)
|
||||
|
||||
// AccessToken holds a GCP access token.
|
||||
type AccessToken struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn int64 `json:"expires_in"`
|
||||
TokenType string `json:"token_type"`
|
||||
}
|
||||
|
||||
type gcpMetadataError struct {
|
||||
err error
|
||||
endpoint string
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func (e *gcpMetadataError) Error() string {
|
||||
return fmt.Sprintf("error retrieving gcp ID token from %s %d: %v", e.endpoint, e.statusCode, e.err)
|
||||
}
|
||||
|
||||
func (e *gcpMetadataError) Unwrap() error { return e.err }
|
||||
|
||||
var (
|
||||
errGCPMetadataNotFound = errors.New("not found")
|
||||
errGCPMetadataInvalidRequest = errors.New("invalid request")
|
||||
errGCPMetadataUnexpected = errors.New("unexpected error")
|
||||
)
|
||||
|
||||
// gcpMetadataAuthPlugin represents authentication via GCP metadata service.
|
||||
type gcpMetadataAuthPlugin struct {
|
||||
AccessTokenPath string `json:"access_token_path"`
|
||||
Audience string `json:"audience"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
IdentityTokenPath string `json:"identity_token_path"`
|
||||
Scopes []string `json:"scopes"`
|
||||
}
|
||||
|
||||
func (ap *gcpMetadataAuthPlugin) NewClient(c Config) (*http.Client, error) {
|
||||
if ap.Audience == "" && len(ap.Scopes) == 0 {
|
||||
return nil, errors.New("audience or scopes is required when gcp metadata is enabled")
|
||||
}
|
||||
|
||||
if ap.Audience != "" && len(ap.Scopes) > 0 {
|
||||
return nil, errors.New("either audience or scopes can be set, not both, when gcp metadata is enabled")
|
||||
}
|
||||
|
||||
if ap.Endpoint == "" {
|
||||
ap.Endpoint = defaultGCPMetadataEndpoint
|
||||
}
|
||||
|
||||
if ap.AccessTokenPath == "" {
|
||||
ap.AccessTokenPath = defaultAccessTokenPath
|
||||
}
|
||||
|
||||
if ap.IdentityTokenPath == "" {
|
||||
ap.IdentityTokenPath = defaultIdentityTokenPath
|
||||
}
|
||||
|
||||
t, err := DefaultTLSConfig(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return DefaultRoundTripperClient(t, *c.ResponseHeaderTimeoutSeconds), nil
|
||||
}
|
||||
|
||||
func (ap *gcpMetadataAuthPlugin) Prepare(req *http.Request) error {
|
||||
var err error
|
||||
var token string
|
||||
|
||||
if ap.Audience != "" {
|
||||
token, err = identityTokenFromMetadataService(ap.Endpoint, ap.IdentityTokenPath, ap.Audience)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error retrieving identity token from gcp metadata service: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(ap.Scopes) != 0 {
|
||||
token, err = accessTokenFromMetadataService(ap.Endpoint, ap.AccessTokenPath, ap.Scopes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error retrieving access token from gcp metadata service: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %v", token))
|
||||
return nil
|
||||
}
|
||||
|
||||
// accessTokenFromMetadataService returns an access token based on the scopes.
|
||||
func accessTokenFromMetadataService(endpoint, path string, scopes []string) (string, error) {
|
||||
s := strings.Join(scopes, ",")
|
||||
|
||||
e := fmt.Sprintf("%s%s?scopes=%s", endpoint, path, s)
|
||||
|
||||
data, err := gcpMetadataServiceRequest(e)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var accessToken AccessToken
|
||||
err = json.Unmarshal(data, &accessToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return accessToken.AccessToken, nil
|
||||
}
|
||||
|
||||
// identityTokenFromMetadataService returns an identity token based on the audience.
|
||||
func identityTokenFromMetadataService(endpoint, path, audience string) (string, error) {
|
||||
e := fmt.Sprintf("%s%s?audience=%s", endpoint, path, audience)
|
||||
|
||||
data, err := gcpMetadataServiceRequest(e)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func gcpMetadataServiceRequest(endpoint string) ([]byte, error) {
|
||||
request, err := http.NewRequest("GET", endpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request.Header.Add("Metadata-Flavor", "Google")
|
||||
|
||||
timeout := time.Duration(5) * time.Second
|
||||
httpClient := http.Client{Timeout: timeout}
|
||||
|
||||
response, err := httpClient.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
switch s := response.StatusCode; s {
|
||||
case 200:
|
||||
break
|
||||
case 400:
|
||||
return nil, &gcpMetadataError{errGCPMetadataInvalidRequest, endpoint, s}
|
||||
case 404:
|
||||
return nil, &gcpMetadataError{errGCPMetadataNotFound, endpoint, s}
|
||||
default:
|
||||
return nil, &gcpMetadataError{errGCPMetadataUnexpected, endpoint, s}
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
-366
@@ -1,366 +0,0 @@
|
||||
// Copyright 2018 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 rest implements a REST client for communicating with remote services.
|
||||
package rest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/open-policy-agent/opa/internal/version"
|
||||
"github.com/open-policy-agent/opa/v1/keys"
|
||||
"github.com/open-policy-agent/opa/v1/logging"
|
||||
"github.com/open-policy-agent/opa/v1/tracing"
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultResponseHeaderTimeoutSeconds = int64(10)
|
||||
defaultResponseSizeLimitBytes = 1024
|
||||
|
||||
grantTypeClientCredentials = "client_credentials"
|
||||
grantTypeJwtBearer = "jwt_bearer"
|
||||
)
|
||||
|
||||
var maskedHeaderKeys = map[string]struct{}{
|
||||
"Authorization": {},
|
||||
"X-Amz-Security-Token": {},
|
||||
}
|
||||
|
||||
// An HTTPAuthPlugin represents a mechanism to construct and configure HTTP authentication for a REST service
|
||||
type HTTPAuthPlugin interface {
|
||||
// implementations can assume NewClient will be called before Prepare
|
||||
NewClient(Config) (*http.Client, error)
|
||||
Prepare(*http.Request) error
|
||||
}
|
||||
|
||||
// Config represents configuration for a REST client.
|
||||
type Config struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
AllowInsecureTLS bool `json:"allow_insecure_tls,omitempty"`
|
||||
ResponseHeaderTimeoutSeconds *int64 `json:"response_header_timeout_seconds,omitempty"`
|
||||
TLS *serverTLSConfig `json:"tls,omitempty"`
|
||||
Credentials struct {
|
||||
Bearer *bearerAuthPlugin `json:"bearer,omitempty"`
|
||||
OAuth2 *oauth2ClientCredentialsAuthPlugin `json:"oauth2,omitempty"`
|
||||
ClientTLS *clientTLSAuthPlugin `json:"client_tls,omitempty"`
|
||||
S3Signing *awsSigningAuthPlugin `json:"s3_signing,omitempty"`
|
||||
GCPMetadata *gcpMetadataAuthPlugin `json:"gcp_metadata,omitempty"`
|
||||
AzureManagedIdentity *azureManagedIdentitiesAuthPlugin `json:"azure_managed_identity,omitempty"`
|
||||
Plugin *string `json:"plugin,omitempty"`
|
||||
} `json:"credentials"`
|
||||
Type string `json:"type,omitempty"`
|
||||
keys map[string]*keys.Config
|
||||
logger logging.Logger
|
||||
}
|
||||
|
||||
// Equal returns true if this client config is equal to the other.
|
||||
func (c *Config) Equal(other *Config) bool {
|
||||
otherWithoutLogger := *other
|
||||
otherWithoutLogger.logger = c.logger
|
||||
return reflect.DeepEqual(c, &otherWithoutLogger)
|
||||
}
|
||||
|
||||
// An AuthPluginLookupFunc can lookup auth plugins by their name.
|
||||
type AuthPluginLookupFunc func(name string) HTTPAuthPlugin
|
||||
|
||||
// AuthPlugin should be used to get an authentication method from the config.
|
||||
func (c *Config) AuthPlugin(lookup AuthPluginLookupFunc) (HTTPAuthPlugin, error) {
|
||||
var candidate HTTPAuthPlugin
|
||||
if c.Credentials.Plugin != nil {
|
||||
if lookup == nil {
|
||||
// if no authPluginLookup function is passed we can't resolve the plugin
|
||||
return nil, errors.New("missing auth plugin lookup function")
|
||||
}
|
||||
|
||||
candidate := lookup(*c.Credentials.Plugin)
|
||||
if candidate == nil {
|
||||
return nil, fmt.Errorf("auth plugin %q not found", *c.Credentials.Plugin)
|
||||
}
|
||||
|
||||
return candidate, nil
|
||||
}
|
||||
// reflection avoids need for this code to change as auth plugins are added
|
||||
s := reflect.ValueOf(c.Credentials)
|
||||
for i := range s.NumField() {
|
||||
if s.Field(i).IsNil() {
|
||||
continue
|
||||
}
|
||||
|
||||
if candidate != nil {
|
||||
return nil, errors.New("a maximum one credential method must be specified")
|
||||
}
|
||||
|
||||
candidate = s.Field(i).Interface().(HTTPAuthPlugin)
|
||||
}
|
||||
|
||||
if candidate == nil {
|
||||
return &defaultAuthPlugin{}, nil
|
||||
}
|
||||
return candidate, nil
|
||||
}
|
||||
|
||||
func (c *Config) authHTTPClient(lookup AuthPluginLookupFunc) (*http.Client, error) {
|
||||
plugin, err := c.AuthPlugin(lookup)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return plugin.NewClient(*c)
|
||||
}
|
||||
|
||||
func (c *Config) authPrepare(req *http.Request, lookup AuthPluginLookupFunc) error {
|
||||
plugin, err := c.AuthPlugin(lookup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return plugin.Prepare(req)
|
||||
}
|
||||
|
||||
// Client implements an HTTP/REST client for communicating with remote
|
||||
// services.
|
||||
type Client struct {
|
||||
bytes *[]byte
|
||||
json *any
|
||||
config Config
|
||||
headers map[string]string
|
||||
authPluginLookup AuthPluginLookupFunc
|
||||
logger logging.Logger
|
||||
loggerFields map[string]any
|
||||
distributedTacingOpts tracing.Options
|
||||
}
|
||||
|
||||
// Name returns an option that overrides the service name on the client.
|
||||
func Name(s string) func(*Client) {
|
||||
return func(c *Client) {
|
||||
c.config.Name = s
|
||||
}
|
||||
}
|
||||
|
||||
// AuthPluginLookup assigns a function to lookup an HTTPAuthPlugin to a new Client.
|
||||
// It's intended to be used when creating a Client using New(). Usually this is passed
|
||||
// the plugins.AuthPlugin func, which retrieves a registered HTTPAuthPlugin from the
|
||||
// plugin manager.
|
||||
func AuthPluginLookup(l AuthPluginLookupFunc) func(*Client) {
|
||||
return func(c *Client) {
|
||||
c.authPluginLookup = l
|
||||
}
|
||||
}
|
||||
|
||||
// Logger assigns a logger to the client
|
||||
func Logger(l logging.Logger) func(*Client) {
|
||||
return func(c *Client) {
|
||||
c.logger = l
|
||||
}
|
||||
}
|
||||
|
||||
// DistributedTracingOpts sets the options to be used by distributed tracing.
|
||||
func DistributedTracingOpts(tr tracing.Options) func(*Client) {
|
||||
return func(c *Client) {
|
||||
c.distributedTacingOpts = tr
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a new Client for config.
|
||||
func New(config []byte, keys map[string]*keys.Config, opts ...func(*Client)) (Client, error) {
|
||||
var parsedConfig Config
|
||||
if err := util.Unmarshal(config, &parsedConfig); err != nil {
|
||||
return Client{}, err
|
||||
}
|
||||
|
||||
parsedConfig.URL = strings.TrimRight(parsedConfig.URL, "/")
|
||||
|
||||
if parsedConfig.ResponseHeaderTimeoutSeconds == nil {
|
||||
timeout := defaultResponseHeaderTimeoutSeconds
|
||||
parsedConfig.ResponseHeaderTimeoutSeconds = &timeout
|
||||
}
|
||||
|
||||
parsedConfig.keys = keys
|
||||
|
||||
client := Client{
|
||||
config: parsedConfig,
|
||||
}
|
||||
|
||||
for _, f := range opts {
|
||||
f(&client)
|
||||
}
|
||||
|
||||
if client.logger == nil {
|
||||
client.logger = logging.Get()
|
||||
}
|
||||
|
||||
client.config.logger = client.logger
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// AuthPluginLookup returns the lookup function to find a custom registered
|
||||
// auth plugin by its name.
|
||||
func (c Client) AuthPluginLookup() AuthPluginLookupFunc {
|
||||
return c.authPluginLookup
|
||||
}
|
||||
|
||||
// Service returns the name of the service this Client is configured for.
|
||||
func (c Client) Service() string {
|
||||
return c.config.Name
|
||||
}
|
||||
|
||||
// Config returns this Client's configuration
|
||||
func (c Client) Config() *Config {
|
||||
return &c.config
|
||||
}
|
||||
|
||||
// SetResponseHeaderTimeout sets the "ResponseHeaderTimeout" in the http client's Transport
|
||||
func (c Client) SetResponseHeaderTimeout(timeout *int64) Client {
|
||||
c.config.ResponseHeaderTimeoutSeconds = timeout
|
||||
return c
|
||||
}
|
||||
|
||||
// Logger returns the logger assigned to the Client
|
||||
func (c Client) Logger() logging.Logger {
|
||||
return c.logger
|
||||
}
|
||||
|
||||
// LoggerFields returns the fields used for log statements used by Client
|
||||
func (c Client) LoggerFields() map[string]any {
|
||||
return c.loggerFields
|
||||
}
|
||||
|
||||
// WithHeader returns a shallow copy of the client with a header to include the
|
||||
// requests.
|
||||
func (c Client) WithHeader(k, v string) Client {
|
||||
if v == "" {
|
||||
return c
|
||||
}
|
||||
if c.headers == nil {
|
||||
c.headers = map[string]string{}
|
||||
}
|
||||
c.headers[k] = v
|
||||
return c
|
||||
}
|
||||
|
||||
// WithJSON returns a shallow copy of the client with the JSON value set as the
|
||||
// message body to include the requests. This function sets the Content-Type
|
||||
// header.
|
||||
func (c Client) WithJSON(body any) Client {
|
||||
c = c.WithHeader("Content-Type", "application/json")
|
||||
c.json = &body
|
||||
return c
|
||||
}
|
||||
|
||||
// WithBytes returns a shallow copy of the client with the bytes set as the
|
||||
// message body to include in the requests.
|
||||
func (c Client) WithBytes(body []byte) Client {
|
||||
c.bytes = &body
|
||||
return c
|
||||
}
|
||||
|
||||
// Do executes a request using the client.
|
||||
func (c Client) Do(ctx context.Context, method, path string) (*http.Response, error) {
|
||||
|
||||
httpClient, err := c.config.authHTTPClient(c.authPluginLookup)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(c.distributedTacingOpts) > 0 {
|
||||
httpClient.Transport = tracing.NewTransport(httpClient.Transport, c.distributedTacingOpts)
|
||||
}
|
||||
|
||||
path = strings.Trim(path, "/")
|
||||
|
||||
var body io.Reader
|
||||
|
||||
if c.bytes != nil {
|
||||
body = bytes.NewReader(*c.bytes)
|
||||
} else if c.json != nil {
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(*c.json); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body = &buf
|
||||
}
|
||||
|
||||
url := c.config.URL + "/" + path
|
||||
req, err := http.NewRequestWithContext(ctx, method, url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
headers := map[string]string{
|
||||
"User-Agent": version.UserAgent,
|
||||
}
|
||||
|
||||
// Copy custom headers from config.
|
||||
maps.Copy(headers, c.config.Headers)
|
||||
|
||||
// Overwrite with headers set directly on client.
|
||||
maps.Copy(headers, c.headers)
|
||||
|
||||
for key, value := range headers {
|
||||
req.Header.Add(key, value)
|
||||
}
|
||||
|
||||
if err = c.config.authPrepare(req, c.authPluginLookup); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.logger.GetLevel() >= logging.Debug {
|
||||
c.loggerFields = map[string]any{
|
||||
"method": method,
|
||||
"url": url,
|
||||
"headers": withMaskedHeaders(req.Header),
|
||||
}
|
||||
|
||||
c.logger.WithFields(c.loggerFields).Debug("Sending request.")
|
||||
}
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
|
||||
if resp != nil && c.logger.GetLevel() >= logging.Debug {
|
||||
// Only log for debug purposes. If an error occurred, the caller should handle
|
||||
// that. In the non-error case, the caller may not do anything.
|
||||
c.loggerFields["status"] = resp.Status
|
||||
c.loggerFields["headers"] = resp.Header
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
dump, err := httputil.DumpResponse(resp, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(dump) < defaultResponseSizeLimitBytes {
|
||||
c.loggerFields["response"] = string(dump)
|
||||
} else {
|
||||
c.loggerFields["response"] = fmt.Sprintf("%v...", string(dump[:defaultResponseSizeLimitBytes]))
|
||||
}
|
||||
}
|
||||
c.logger.WithFields(c.loggerFields).Debug("Received response.")
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func withMaskedHeaders(headers http.Header) http.Header {
|
||||
masked := make(http.Header)
|
||||
for k, v := range headers {
|
||||
if _, ok := maskedHeaderKeys[k]; ok {
|
||||
masked.Set(k, "REDACTED")
|
||||
} else {
|
||||
masked[k] = v
|
||||
}
|
||||
}
|
||||
return masked
|
||||
}
|
||||
+2
-14
@@ -27,7 +27,6 @@ import (
|
||||
"github.com/open-policy-agent/opa/v1/loader"
|
||||
"github.com/open-policy-agent/opa/v1/loader/filter"
|
||||
"github.com/open-policy-agent/opa/v1/metrics"
|
||||
"github.com/open-policy-agent/opa/v1/plugins"
|
||||
"github.com/open-policy-agent/opa/v1/resolver"
|
||||
"github.com/open-policy-agent/opa/v1/storage"
|
||||
"github.com/open-policy-agent/opa/v1/storage/inmem"
|
||||
@@ -666,8 +665,6 @@ type Rego struct {
|
||||
enablePrintStatements bool
|
||||
distributedTracingOpts tracing.Options
|
||||
strict bool
|
||||
pluginMgr *plugins.Manager
|
||||
plugins []TargetPlugin
|
||||
targetPrepState TargetPluginEval
|
||||
regoVersion ast.RegoVersion
|
||||
compilerHook func(*ast.Compiler)
|
||||
@@ -1428,15 +1425,6 @@ func New(options ...func(r *Rego)) *Rego {
|
||||
r.generateJSON = generateJSON
|
||||
}
|
||||
|
||||
if r.pluginMgr != nil {
|
||||
for _, pluginName := range r.pluginMgr.Plugins() {
|
||||
p := r.pluginMgr.Plugin(pluginName)
|
||||
if p0, ok := p.(TargetPlugin); ok {
|
||||
r.plugins = append(r.plugins, p0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if t := r.targetPlugin(r.target); t != nil {
|
||||
r.compiler = r.compiler.WithEvalMode(ast.EvalModeIR)
|
||||
}
|
||||
@@ -2241,7 +2229,7 @@ func (r *Rego) compileQuery(query ast.Body, imports []*ast.Import, _ metrics.Met
|
||||
WithStrict(false)
|
||||
|
||||
for _, extra := range extras {
|
||||
qc = qc.WithStageAfter(extra.after, extra.stage)
|
||||
qc = qc.WithStageAfterID(extra.after, extra.stage)
|
||||
}
|
||||
|
||||
compiled, err := qc.Compile(query)
|
||||
@@ -2889,7 +2877,7 @@ func (m rawModule) ParseWithOpts(opts ast.ParserOptions) (*ast.Module, error) {
|
||||
}
|
||||
|
||||
type extraStage struct {
|
||||
after string
|
||||
after ast.StageID
|
||||
stage ast.QueryCompilerStageDefinition
|
||||
}
|
||||
|
||||
|
||||
+15
-3
@@ -79,12 +79,24 @@ func (ev *ExpressionValue) String() string {
|
||||
// return `true` for a query like `data.authz.allow = x`, which always has result
|
||||
// set element with value true, but could also have a binding `x: false`.
|
||||
func (rs ResultSet) Allowed() bool {
|
||||
x, _ := ResultValue[bool](rs)
|
||||
return x
|
||||
}
|
||||
|
||||
// ResultValue is a helper function that'll return a value of type T if all of
|
||||
// these conditions hold:
|
||||
// - the result set only has one element
|
||||
// - there is only one expression in the result set's only element
|
||||
// - that expression has type T
|
||||
// - there are no bindings.
|
||||
func ResultValue[T any](rs ResultSet) (T, bool) {
|
||||
var zero T
|
||||
if len(rs) == 1 && len(rs[0].Bindings) == 0 {
|
||||
if exprs := rs[0].Expressions; len(exprs) == 1 {
|
||||
if b, ok := exprs[0].Value.(bool); ok {
|
||||
return b
|
||||
if v, ok := exprs[0].Value.(T); ok {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
return zero, false
|
||||
}
|
||||
|
||||
+8
@@ -54,6 +54,14 @@ type NonEmptyer interface {
|
||||
NonEmpty(context.Context, Transaction) func([]string) (bool, error)
|
||||
}
|
||||
|
||||
// Closer is an optional interface that storage implementations can implement
|
||||
// to perform cleanup operations when the store is being shut down.
|
||||
// If a Store implements this interface, Close will be called during
|
||||
// graceful shutdown of the OPA runtime.
|
||||
type Closer interface {
|
||||
Close(context.Context) error
|
||||
}
|
||||
|
||||
// TransactionParams describes a new transaction.
|
||||
type TransactionParams struct {
|
||||
|
||||
|
||||
+5
-3
@@ -95,12 +95,14 @@ func builtinArraySlice(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Te
|
||||
return err
|
||||
}
|
||||
|
||||
l := arr.Len()
|
||||
|
||||
// Clamp stopIndex to avoid out-of-range errors. If negative, clamp to zero.
|
||||
// Otherwise, clamp to length of array.
|
||||
if stopIndex < 0 {
|
||||
stopIndex = 0
|
||||
} else if stopIndex > arr.Len() {
|
||||
stopIndex = arr.Len()
|
||||
} else if stopIndex > l {
|
||||
stopIndex = l
|
||||
}
|
||||
|
||||
// Clamp startIndex to avoid out-of-range errors. If negative, clamp to zero.
|
||||
@@ -111,7 +113,7 @@ func builtinArraySlice(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Te
|
||||
startIndex = stopIndex
|
||||
}
|
||||
|
||||
if startIndex == 0 && stopIndex >= arr.Len() {
|
||||
if startIndex == 0 && stopIndex >= l {
|
||||
return iter(operands[0])
|
||||
}
|
||||
|
||||
|
||||
+77
-14
@@ -40,6 +40,14 @@ func newBindings(id uint64, instr *Instrumentation) *bindings {
|
||||
return &bindings{id, values, instr}
|
||||
}
|
||||
|
||||
// newBindingsWithSize creates bindings pre-sized for the expected number of entries.
|
||||
// This avoids over-allocation when the binding count is known in advance (e.g., function arguments).
|
||||
// For sizeHint <= maxLinearScan, it uses array mode; for larger hints, it pre-allocates a map.
|
||||
func newBindingsWithSize(id uint64, instr *Instrumentation, sizeHint int) *bindings {
|
||||
values := newBindingsArrayHashmapWithSize(sizeHint)
|
||||
return &bindings{id, values, instr}
|
||||
}
|
||||
|
||||
func (u *bindings) Iter(caller *bindings, iter func(*ast.Term, *ast.Term) error) error {
|
||||
|
||||
var err error
|
||||
@@ -300,12 +308,16 @@ func (vis namespacingVisitor) namespaceTerm(a *ast.Term) *ast.Term {
|
||||
|
||||
const maxLinearScan = 16
|
||||
|
||||
// bindingsArrayHashMap uses an array with linear scan instead
|
||||
// bindingsArrayHashMap uses a dynamically growing slice with linear scan instead
|
||||
// of a hash map for smaller # of entries. Hash maps start to
|
||||
// show off their performance advantage only after 16 keys.
|
||||
//
|
||||
// Memory optimization: The slice grows incrementally (2 -> 4 -> 8 -> 16) to avoid
|
||||
// wasting memory when only a few bindings are used. This is critical for scenarios
|
||||
// like comprehensions and functions with few arguments that are called thousands of times.
|
||||
type bindingsArrayHashmap struct {
|
||||
n int // Entries in the array.
|
||||
a *[maxLinearScan]bindingArrayKeyValue
|
||||
n int // Entries in the slice.
|
||||
a []bindingArrayKeyValue
|
||||
m map[ast.Var]bindingArrayKeyValue
|
||||
}
|
||||
|
||||
@@ -318,29 +330,74 @@ func newBindingsArrayHashmap() bindingsArrayHashmap {
|
||||
return bindingsArrayHashmap{}
|
||||
}
|
||||
|
||||
// newBindingsArrayHashmapWithSize creates a bindingsArrayHashmap pre-sized for the expected number of entries.
|
||||
// This optimization reduces memory waste when the binding count is known in advance.
|
||||
//
|
||||
// Size selection strategy:
|
||||
// - sizeHint == 0: lazy allocation (no pre-allocation)
|
||||
// - sizeHint <= maxLinearScan: pre-allocate slice with exact capacity to avoid reallocation
|
||||
// - sizeHint > maxLinearScan: pre-allocate map with exact capacity
|
||||
//
|
||||
// Memory impact example:
|
||||
// - Without hint: dynamic growth 0 -> 2 -> 4 -> 8 -> 16 (saves memory for small counts)
|
||||
// - With hint=2: pre-allocates slice with capacity 2 (exact fit, no waste)
|
||||
// - With hint=20: pre-allocates map with capacity 20 (saves array allocation + reallocation)
|
||||
func newBindingsArrayHashmapWithSize(sizeHint int) bindingsArrayHashmap {
|
||||
if sizeHint <= 0 {
|
||||
// For unknown sizes, use default lazy allocation with dynamic growth.
|
||||
return bindingsArrayHashmap{}
|
||||
}
|
||||
|
||||
if sizeHint <= maxLinearScan {
|
||||
// For small known sizes, pre-allocate slice with exact capacity to avoid growth overhead.
|
||||
return bindingsArrayHashmap{
|
||||
a: make([]bindingArrayKeyValue, 0, sizeHint),
|
||||
}
|
||||
}
|
||||
|
||||
// For larger sizes, pre-allocate map to avoid array allocation + transition cost.
|
||||
return bindingsArrayHashmap{
|
||||
m: make(map[ast.Var]bindingArrayKeyValue, sizeHint),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *bindingsArrayHashmap) Put(key *ast.Term, value value) {
|
||||
if b.m == nil {
|
||||
if b.a == nil {
|
||||
b.a = new([maxLinearScan]bindingArrayKeyValue)
|
||||
} else if i := b.find(key); i >= 0 {
|
||||
// Check if key already exists and update value
|
||||
if i := b.find(key); i >= 0 {
|
||||
b.a[i].value = value
|
||||
return
|
||||
}
|
||||
|
||||
// Still room in slice mode (< maxLinearScan)
|
||||
if b.n < maxLinearScan {
|
||||
b.a[b.n] = bindingArrayKeyValue{key, value}
|
||||
// Grow slice if needed using exponential growth strategy
|
||||
if b.n == cap(b.a) {
|
||||
newCap := cap(b.a) * 2
|
||||
if newCap == 0 {
|
||||
newCap = 2 // Start with 2 elements
|
||||
}
|
||||
if newCap > maxLinearScan {
|
||||
newCap = maxLinearScan
|
||||
}
|
||||
newA := make([]bindingArrayKeyValue, b.n, newCap)
|
||||
copy(newA, b.a)
|
||||
b.a = newA
|
||||
}
|
||||
b.a = append(b.a, bindingArrayKeyValue{key, value})
|
||||
b.n++
|
||||
return
|
||||
}
|
||||
|
||||
// Array is full, revert to using the hash map instead.
|
||||
|
||||
// Slice is full (reached maxLinearScan), transition to map mode.
|
||||
b.m = make(map[ast.Var]bindingArrayKeyValue, maxLinearScan+1)
|
||||
for _, kv := range *b.a {
|
||||
for _, kv := range b.a {
|
||||
b.m[kv.key.Value.(ast.Var)] = bindingArrayKeyValue{kv.key, kv.value}
|
||||
}
|
||||
b.m[key.Value.(ast.Var)] = bindingArrayKeyValue{key, value}
|
||||
|
||||
// Clear slice to allow GC
|
||||
b.a = nil
|
||||
b.n = 0
|
||||
return
|
||||
}
|
||||
@@ -372,7 +429,8 @@ func (b *bindingsArrayHashmap) Delete(key *ast.Term) {
|
||||
if i < n {
|
||||
b.a[i] = b.a[n]
|
||||
}
|
||||
|
||||
// Shrink slice to reflect deletion
|
||||
b.a = b.a[:n]
|
||||
b.n = n
|
||||
}
|
||||
return
|
||||
@@ -383,9 +441,11 @@ func (b *bindingsArrayHashmap) Delete(key *ast.Term) {
|
||||
|
||||
func (b *bindingsArrayHashmap) Iter(f func(k *ast.Term, v value) bool) {
|
||||
if b.m == nil {
|
||||
for i := range b.n {
|
||||
if f(b.a[i].key, b.a[i].value) {
|
||||
return
|
||||
if b.a != nil {
|
||||
for i := range b.n {
|
||||
if f(b.a[i].key, b.a[i].value) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -399,6 +459,9 @@ func (b *bindingsArrayHashmap) Iter(f func(k *ast.Term, v value) bool) {
|
||||
}
|
||||
|
||||
func (b *bindingsArrayHashmap) find(key *ast.Term) int {
|
||||
if b.a == nil || b.n == 0 {
|
||||
return -1
|
||||
}
|
||||
v := key.Value.(ast.Var)
|
||||
for i := range b.n {
|
||||
if b.a[i].key.Value.(ast.Var) == v {
|
||||
|
||||
+20
-5
@@ -32,7 +32,7 @@ func getDefaultInterQueryBuiltinValueCacheConfig(name string) *NamedValueCacheCo
|
||||
|
||||
// RegisterDefaultInterQueryBuiltinValueCacheConfig registers a default configuration for the inter-query value cache;
|
||||
// used when none has been explicitly configured.
|
||||
// To disable a named cache when not configured, pass a nil config.
|
||||
// To disable a named cache when not configured, pass a config with the disabled value set to true.
|
||||
func RegisterDefaultInterQueryBuiltinValueCacheConfig(name string, config *NamedValueCacheConfig) {
|
||||
interQueryBuiltinValueCacheDefaultConfigs[name] = config
|
||||
}
|
||||
@@ -58,7 +58,8 @@ func (c *Config) Clone() *Config {
|
||||
// NamedValueCacheConfig represents the configuration of a named cache that built-in functions can utilize.
|
||||
// A default configuration to be used if not explicitly configured can be registered using RegisterDefaultInterQueryBuiltinValueCacheConfig.
|
||||
type NamedValueCacheConfig struct {
|
||||
MaxNumEntries *int `json:"max_num_entries,omitempty"`
|
||||
MaxNumEntries *int `json:"max_num_entries,omitempty"`
|
||||
Disabled *bool `json:"disabled,omitempty"`
|
||||
}
|
||||
|
||||
// Clone creates a deep copy of NamedValueCacheConfig.
|
||||
@@ -73,6 +74,10 @@ func (n *NamedValueCacheConfig) Clone() *NamedValueCacheConfig {
|
||||
maxEntries := *n.MaxNumEntries
|
||||
clone.MaxNumEntries = &maxEntries
|
||||
}
|
||||
if n.Disabled != nil {
|
||||
disabled := *n.Disabled
|
||||
clone.Disabled = &disabled
|
||||
}
|
||||
|
||||
return clone
|
||||
}
|
||||
@@ -220,9 +225,15 @@ func (c *Config) validateAndInjectDefaults() error {
|
||||
}
|
||||
|
||||
for name, namedConfig := range c.InterQueryBuiltinValueCache.NamedCacheConfigs {
|
||||
numEntries := *namedConfig.MaxNumEntries
|
||||
if numEntries < 0 {
|
||||
return fmt.Errorf("invalid max_num_entries %v for named cache %v", numEntries, name)
|
||||
if namedConfig == nil || (namedConfig.MaxNumEntries == nil && namedConfig.Disabled == nil) {
|
||||
return fmt.Errorf("missing configuration for named cache %v", name)
|
||||
}
|
||||
|
||||
if namedConfig.MaxNumEntries != nil {
|
||||
numEntries := *namedConfig.MaxNumEntries
|
||||
if numEntries < 0 {
|
||||
return fmt.Errorf("invalid max_num_entries %v for named cache %v", numEntries, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -605,6 +616,10 @@ func (c *interQueryBuiltinValueCache) GetCache(name string) InterQueryValueCache
|
||||
return nil
|
||||
}
|
||||
|
||||
if config.Disabled != nil && *config.Disabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
nc = &interQueryValueCacheBucket{
|
||||
items: *newItemsMap(),
|
||||
config: config,
|
||||
|
||||
Generated
Vendored
+2
-11
@@ -9,6 +9,7 @@ import (
|
||||
"sort"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/ast"
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// CopyPropagator implements a simple copy propagation optimization to remove
|
||||
@@ -49,17 +50,7 @@ func (l *localVarGenerator) Generate() ast.Var {
|
||||
// New returns a new CopyPropagator that optimizes queries while preserving vars
|
||||
// in the livevars set.
|
||||
func New(livevars ast.VarSet) *CopyPropagator {
|
||||
|
||||
sorted := make([]ast.Var, 0, len(livevars))
|
||||
for v := range livevars {
|
||||
sorted = append(sorted, v)
|
||||
}
|
||||
|
||||
sort.Slice(sorted, func(i, j int) bool {
|
||||
return sorted[i].Compare(sorted[j]) < 0
|
||||
})
|
||||
|
||||
return &CopyPropagator{livevars: livevars, sorted: sorted, localvargen: &localVarGenerator{}}
|
||||
return &CopyPropagator{livevars: livevars, sorted: util.KeysSorted(livevars), localvargen: &localVarGenerator{}}
|
||||
}
|
||||
|
||||
// WithEnsureNonEmptyBody configures p to ensure that results are always non-empty.
|
||||
|
||||
+120
-55
@@ -32,6 +32,13 @@ type queryIDFactory struct {
|
||||
curr uint64
|
||||
}
|
||||
|
||||
type biunifyArraysRecParams struct {
|
||||
a, b *ast.Array
|
||||
b1, b2 *bindings
|
||||
iter unifyIterator
|
||||
idx int
|
||||
}
|
||||
|
||||
// Note: The first call to Next() returns 0.
|
||||
func (f *queryIDFactory) Next() uint64 {
|
||||
curr := f.curr
|
||||
@@ -147,10 +154,11 @@ func (ep *evbp) Get() *evalBuiltin {
|
||||
}
|
||||
|
||||
var (
|
||||
evalPool = util.NewSyncPool[eval]()
|
||||
deecPool = util.NewSyncPool[deferredEarlyExitContainer]()
|
||||
resolverPool = util.NewSyncPool[evalResolver]()
|
||||
evalFuncPool = &evfp{
|
||||
evalPool = util.NewSyncPool[eval]()
|
||||
deecPool = util.NewSyncPool[deferredEarlyExitContainer]()
|
||||
resolverPool = util.NewSyncPool[evalResolver]()
|
||||
arraysRecPool = util.NewSyncPool[biunifyArraysRecParams]()
|
||||
evalFuncPool = &evfp{
|
||||
pool: sync.Pool{
|
||||
New: func() any {
|
||||
return &evalFunc{}
|
||||
@@ -222,12 +230,14 @@ func (e *eval) closure(query ast.Body, cpy *eval) {
|
||||
cpy.findOne = false
|
||||
}
|
||||
|
||||
func (e *eval) child(query ast.Body, cpy *eval) {
|
||||
// childWithBindingSizeHint creates a child evaluator with bindings pre-sized for the expected number of variables.
|
||||
// This reduces memory waste when evaluating functions or rules with known argument counts.
|
||||
func (e *eval) childWithBindingSizeHint(query ast.Body, cpy *eval, sizeHint int) {
|
||||
*cpy = *e
|
||||
cpy.index = 0
|
||||
cpy.query = query
|
||||
cpy.queryID = cpy.queryIDFact.Next()
|
||||
cpy.bindings = newBindings(cpy.queryID, e.instr)
|
||||
cpy.bindings = newBindingsWithSize(cpy.queryID, e.instr, sizeHint)
|
||||
cpy.parent = e
|
||||
cpy.findOne = false
|
||||
}
|
||||
@@ -922,7 +932,7 @@ func (e *eval) evalCall(terms []*ast.Term, iter unifyIterator) error {
|
||||
mock, mocked := e.functionMocks.Get(ref)
|
||||
if mocked {
|
||||
if m, ok := mock.Value.(ast.Ref); ok && isFunction(e.compiler.TypeEnv, m) { // builtin or data function
|
||||
mockCall := append([]*ast.Term{ast.NewTerm(m)}, terms[1:]...)
|
||||
mockCall := append([]*ast.Term{mock}, terms[1:]...)
|
||||
|
||||
e.functionMocks.Push()
|
||||
err := e.evalCall(mockCall, func() error {
|
||||
@@ -1086,7 +1096,12 @@ func (e *eval) biunify(a, b *ast.Term, b1, b2 *bindings, iter unifyIterator) err
|
||||
return e.biunifyValues(a, b, b1, b2, iter)
|
||||
case *ast.Array:
|
||||
if vA.Len() == vB.Len() {
|
||||
return e.biunifyArraysRec(vA, vB, b1, b2, iter, 0)
|
||||
params := arraysRecPool.Get()
|
||||
params.a, params.b = vA, vB
|
||||
params.b1, params.b2 = b1, b2
|
||||
params.iter = iter
|
||||
params.idx = 0
|
||||
return e.biunifyArraysRec(params)
|
||||
}
|
||||
}
|
||||
case ast.Object:
|
||||
@@ -1102,12 +1117,15 @@ func (e *eval) biunify(a, b *ast.Term, b1, b2 *bindings, iter unifyIterator) err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *eval) biunifyArraysRec(a, b *ast.Array, b1, b2 *bindings, iter unifyIterator, idx int) error {
|
||||
if idx == a.Len() {
|
||||
return iter()
|
||||
func (e *eval) biunifyArraysRec(params *biunifyArraysRecParams) error {
|
||||
if params.idx == params.a.Len() {
|
||||
err := params.iter()
|
||||
arraysRecPool.Put(params)
|
||||
return err
|
||||
}
|
||||
return e.biunify(a.Elem(idx), b.Elem(idx), b1, b2, func() error {
|
||||
return e.biunifyArraysRec(a, b, b1, b2, iter, idx+1)
|
||||
return e.biunify(params.a.Elem(params.idx), params.b.Elem(params.idx), params.b1, params.b2, func() error {
|
||||
params.idx++
|
||||
return e.biunifyArraysRec(params)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1243,7 +1261,6 @@ func (e *eval) biunifyValues(a, b *ast.Term, b1, b2 *bindings, iter unifyIterato
|
||||
}
|
||||
|
||||
func (e *eval) biunifyRef(a, b *ast.Term, b1, b2 *bindings, iter unifyIterator) error {
|
||||
|
||||
ref := a.Value.(ast.Ref)
|
||||
|
||||
if ref[0].Equal(ast.DefaultRootDocument) {
|
||||
@@ -1367,7 +1384,7 @@ func (e *eval) buildComprehensionCacheArray(x *ast.ArrayComprehension, keys []*a
|
||||
child := evalPool.Get()
|
||||
defer evalPool.Put(child)
|
||||
|
||||
e.child(x.Body, child)
|
||||
e.childWithBindingSizeHint(x.Body, child, ast.EstimateBodyBindingCount(x.Body))
|
||||
node := newComprehensionCacheElem()
|
||||
return node, child.Run(func(child *eval) error {
|
||||
values := make([]*ast.Term, len(keys))
|
||||
@@ -1389,7 +1406,7 @@ func (e *eval) buildComprehensionCacheSet(x *ast.SetComprehension, keys []*ast.T
|
||||
child := evalPool.Get()
|
||||
defer evalPool.Put(child)
|
||||
|
||||
e.child(x.Body, child)
|
||||
e.childWithBindingSizeHint(x.Body, child, ast.EstimateBodyBindingCount(x.Body))
|
||||
node := newComprehensionCacheElem()
|
||||
return node, child.Run(func(child *eval) error {
|
||||
values := make([]*ast.Term, len(keys))
|
||||
@@ -1412,7 +1429,7 @@ func (e *eval) buildComprehensionCacheObject(x *ast.ObjectComprehension, keys []
|
||||
child := evalPool.Get()
|
||||
defer evalPool.Put(child)
|
||||
|
||||
e.child(x.Body, child)
|
||||
e.childWithBindingSizeHint(x.Body, child, ast.EstimateBodyBindingCount(x.Body))
|
||||
node := newComprehensionCacheElem()
|
||||
return node, child.Run(func(child *eval) error {
|
||||
values := make([]*ast.Term, len(keys))
|
||||
@@ -1505,23 +1522,37 @@ func (e *eval) biunifyComprehensionArray(x *ast.ArrayComprehension, b *ast.Term,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(elements) == 0 {
|
||||
return e.biunify(ast.InternedEmptyArray, b, b1, b2, iter)
|
||||
}
|
||||
|
||||
return e.biunify(ast.NewTerm(ast.NewArray(elements...)), b, b1, b2, iter)
|
||||
}
|
||||
|
||||
func (e *eval) biunifyComprehensionSet(x *ast.SetComprehension, b *ast.Term, b1, b2 *bindings, iter unifyIterator) error {
|
||||
result := ast.NewSet()
|
||||
child := evalPool.Get()
|
||||
|
||||
e.closure(x.Body, child)
|
||||
defer evalPool.Put(child)
|
||||
|
||||
var result ast.Set
|
||||
err := child.Run(func(child *eval) error {
|
||||
result.Add(child.bindings.Plug(x.Term))
|
||||
if result == nil {
|
||||
result = ast.NewSet(child.bindings.Plug(x.Term))
|
||||
} else {
|
||||
result.Add(child.bindings.Plug(x.Term))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
return e.biunify(ast.InternedEmptySet, b, b1, b2, iter)
|
||||
}
|
||||
|
||||
return e.biunify(ast.NewTerm(result), b, b1, b2, iter)
|
||||
}
|
||||
|
||||
@@ -1531,21 +1562,28 @@ func (e *eval) biunifyComprehensionObject(x *ast.ObjectComprehension, b *ast.Ter
|
||||
|
||||
e.closure(x.Body, child)
|
||||
|
||||
result := ast.NewObject()
|
||||
|
||||
var result ast.Object
|
||||
err := child.Run(func(child *eval) error {
|
||||
key := child.bindings.Plug(x.Key)
|
||||
value := child.bindings.Plug(x.Value)
|
||||
exist := result.Get(key)
|
||||
if exist != nil && !exist.Equal(value) {
|
||||
return objectDocKeyConflictErr(x.Key.Location)
|
||||
if result == nil {
|
||||
result = ast.NewObject(ast.Item(key, value))
|
||||
} else {
|
||||
if exist := result.Get(key); exist != nil && !exist.Equal(value) {
|
||||
return objectDocKeyConflictErr(x.Key.Location)
|
||||
}
|
||||
result.Insert(key, value)
|
||||
}
|
||||
result.Insert(key, value)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
return e.biunify(ast.InternedEmptyObject, b, b1, b2, iter)
|
||||
}
|
||||
|
||||
return e.biunify(ast.NewTerm(result), b, b1, b2, iter)
|
||||
}
|
||||
|
||||
@@ -1664,7 +1702,7 @@ func (e *eval) getRules(ref ast.Ref, args []*ast.Term) (*ast.IndexResult, error)
|
||||
e.instr.startTimer(evalOpRuleIndex)
|
||||
defer e.instr.stopTimer(evalOpRuleIndex)
|
||||
|
||||
index := e.compiler.RuleIndex(ref)
|
||||
index := e.ruleIndex(ref)
|
||||
if index == nil {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -1715,6 +1753,11 @@ func (e *eval) getRules(ref ast.Ref, args []*ast.Term) (*ast.IndexResult, error)
|
||||
return result, err
|
||||
}
|
||||
|
||||
// ruleIndex performs a lookup for a RuleIndex in the compiler's RuleTree.
|
||||
func (e *eval) ruleIndex(ref ast.Ref) ast.RuleIndex {
|
||||
return e.compiler.RuleIndex(ref)
|
||||
}
|
||||
|
||||
func (e *eval) Resolve(ref ast.Ref) (ast.Value, error) {
|
||||
return (&evalResolver{e: e}).Resolve(ref)
|
||||
}
|
||||
@@ -2247,7 +2290,11 @@ func (e *evalFunc) evalOneRule(iter unifyIterator, rule *ast.Rule, args []*ast.T
|
||||
child := evalPool.Get()
|
||||
defer evalPool.Put(child)
|
||||
|
||||
e.e.child(rule.Body, child)
|
||||
// Optimization: pre-size bindings based on function argument count to reduce memory waste.
|
||||
// Function argument count is known at compile time and most functions have < 10 arguments.
|
||||
// This avoids allocating the default 16-slot array when only 2-3 bindings are needed.
|
||||
sizeHint := len(args)
|
||||
e.e.childWithBindingSizeHint(rule.Body, child, sizeHint)
|
||||
child.findOne = findOne
|
||||
|
||||
var result *ast.Term
|
||||
@@ -2337,7 +2384,7 @@ func (e *evalFunc) partialEvalSupportRule(rule *ast.Rule, path ast.Ref) error {
|
||||
child := evalPool.Get()
|
||||
defer evalPool.Put(child)
|
||||
|
||||
e.e.child(rule.Body, child)
|
||||
e.e.childWithBindingSizeHint(rule.Body, child, ast.EstimateBodyBindingCount(rule.Body))
|
||||
child.traceEnter(rule)
|
||||
|
||||
e.e.saveStack.PushQuery(nil)
|
||||
@@ -2612,9 +2659,7 @@ func (e evalTree) leaves(plugged ast.Ref, node *ast.TreeNode) (ast.Object, error
|
||||
result := ast.NewObject()
|
||||
|
||||
for _, k := range node.Sorted {
|
||||
|
||||
child := node.Children[k]
|
||||
|
||||
if child.Hide {
|
||||
continue
|
||||
}
|
||||
@@ -2877,7 +2922,7 @@ func (e evalVirtualPartial) evalAllRulesNoCache(rules []*ast.Rule) (*ast.Term, e
|
||||
defer evalPool.Put(child)
|
||||
|
||||
for _, rule := range rules {
|
||||
e.e.child(rule.Body, child)
|
||||
e.e.childWithBindingSizeHint(rule.Body, child, ast.EstimateBodyBindingCount(rule.Body))
|
||||
child.traceEnter(rule)
|
||||
err := child.eval(func(*eval) error {
|
||||
child.traceExit(rule)
|
||||
@@ -2913,7 +2958,7 @@ func (e evalVirtualPartial) evalOneRulePreUnify(iter unifyIterator, rule *ast.Ru
|
||||
child := evalPool.Get()
|
||||
defer evalPool.Put(child)
|
||||
|
||||
e.e.child(rule.Body, child)
|
||||
e.e.childWithBindingSizeHint(rule.Body, child, ast.EstimateBodyBindingCount(rule.Body))
|
||||
|
||||
child.traceEnter(rule)
|
||||
var defined bool
|
||||
@@ -3008,7 +3053,7 @@ func (e evalVirtualPartial) evalOneRulePostUnify(iter unifyIterator, rule *ast.R
|
||||
child := evalPool.Get()
|
||||
defer evalPool.Put(child)
|
||||
|
||||
e.e.child(rule.Body, child)
|
||||
e.e.childWithBindingSizeHint(rule.Body, child, ast.EstimateBodyBindingCount(rule.Body))
|
||||
|
||||
child.traceEnter(rule)
|
||||
var defined bool
|
||||
@@ -3095,7 +3140,7 @@ func (e evalVirtualPartial) partialEvalSupportRule(rule *ast.Rule, _ ast.Ref) (b
|
||||
child := evalPool.Get()
|
||||
defer evalPool.Put(child)
|
||||
|
||||
e.e.child(rule.Body, child)
|
||||
e.e.childWithBindingSizeHint(rule.Body, child, ast.EstimateBodyBindingCount(rule.Body))
|
||||
child.traceEnter(rule)
|
||||
|
||||
e.e.saveStack.PushQuery(nil)
|
||||
@@ -3378,7 +3423,12 @@ func getNestedObject(ref ast.Ref, rootObj *ast.Object, b *bindings, l *ast.Locat
|
||||
}
|
||||
|
||||
func hasCollisions(path ast.Ref, visitedRefs *[]ast.Ref, b *bindings) bool {
|
||||
collisionPathTerm := b.Plug(ast.NewTerm(path))
|
||||
// Avoid allocating a new term just for the sake of a lookup
|
||||
term := ast.TermPtrPool.Get()
|
||||
term.Value = path
|
||||
collisionPathTerm := b.Plug(term)
|
||||
ast.TermPtrPool.Put(term)
|
||||
|
||||
collisionPath := collisionPathTerm.Value.(ast.Ref)
|
||||
for _, c := range *visitedRefs {
|
||||
if collisionPath.HasPrefix(c) && !collisionPath.Equal(c) {
|
||||
@@ -3390,15 +3440,15 @@ func hasCollisions(path ast.Ref, visitedRefs *[]ast.Ref, b *bindings) bool {
|
||||
}
|
||||
|
||||
func (e evalVirtualPartial) reduce(rule *ast.Rule, b *bindings, result *ast.Term, visitedRefs *[]ast.Ref) (*ast.Term, bool, error) {
|
||||
|
||||
var exists bool
|
||||
head := rule.Head
|
||||
|
||||
switch v := result.Value.(type) {
|
||||
case ast.Set:
|
||||
key := b.Plug(head.Key)
|
||||
exists = v.Contains(key)
|
||||
v.Add(key)
|
||||
if exists = v.Contains(key); !exists {
|
||||
v.Add(key)
|
||||
}
|
||||
case ast.Object:
|
||||
// data.p.q[r].s.t := 42 {...}
|
||||
// |----|-|
|
||||
@@ -3580,7 +3630,7 @@ func (e evalVirtualComplete) evalValueRule(iter unifyIterator, rule *ast.Rule, p
|
||||
child := evalPool.Get()
|
||||
defer evalPool.Put(child)
|
||||
|
||||
e.e.child(rule.Body, child)
|
||||
e.e.childWithBindingSizeHint(rule.Body, child, ast.EstimateBodyBindingCount(rule.Body))
|
||||
child.findOne = findOne
|
||||
child.traceEnter(rule)
|
||||
var result *ast.Term
|
||||
@@ -3619,7 +3669,7 @@ func (e evalVirtualComplete) partialEval(iter unifyIterator) error {
|
||||
defer evalPool.Put(child)
|
||||
|
||||
for _, rule := range e.ir.Rules {
|
||||
e.e.child(rule.Body, child)
|
||||
e.e.childWithBindingSizeHint(rule.Body, child, ast.EstimateBodyBindingCount(rule.Body))
|
||||
child.traceEnter(rule)
|
||||
|
||||
err := child.eval(func(child *eval) error {
|
||||
@@ -3644,17 +3694,22 @@ func (e evalVirtualComplete) partialEval(iter unifyIterator) error {
|
||||
}
|
||||
|
||||
func (e evalVirtualComplete) partialEvalSupport(iter unifyIterator) error {
|
||||
|
||||
path := e.e.namespaceRef(e.plugged[:e.pos+1])
|
||||
originalPath := e.plugged[:e.pos+1]
|
||||
namespacePath := e.e.namespaceRef(originalPath)
|
||||
term := ast.NewTerm(e.e.namespaceRef(e.ref))
|
||||
|
||||
var defined bool
|
||||
|
||||
if e.e.saveSupport.Exists(path) {
|
||||
if e.e.saveSupport.Exists(namespacePath) {
|
||||
defined = true
|
||||
} else {
|
||||
for i := range e.ir.Rules {
|
||||
ok, err := e.partialEvalSupportRule(e.ir.Rules[i], path)
|
||||
// Split the rule from the package
|
||||
ruleRef := originalPath.Copy()[len(e.ir.Rules[i].Module.Package.Path):]
|
||||
ruleRef[0].Value = ast.Var(ruleRef[0].Value.(ast.String))
|
||||
// Get the namespaced package path without the rule
|
||||
packagePath := namespacePath.Copy()[:len(namespacePath)-len(ruleRef)]
|
||||
ok, err := e.partialEvalSupportRule(e.ir.Rules[i], packagePath, ruleRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -3664,7 +3719,12 @@ func (e evalVirtualComplete) partialEvalSupport(iter unifyIterator) error {
|
||||
}
|
||||
|
||||
if e.ir.Default != nil {
|
||||
ok, err := e.partialEvalSupportRule(e.ir.Default, path)
|
||||
// Split the rule from the package
|
||||
ruleRef := originalPath.Copy()[len(e.ir.Default.Module.Package.Path):]
|
||||
ruleRef[0].Value = ast.Var(ruleRef[0].Value.(ast.String))
|
||||
// Get the namespaced package path without the rule
|
||||
packagePath := namespacePath.Copy()[:len(namespacePath)-len(ruleRef)]
|
||||
ok, err := e.partialEvalSupportRule(e.ir.Default, packagePath, ruleRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -3681,11 +3741,11 @@ func (e evalVirtualComplete) partialEvalSupport(iter unifyIterator) error {
|
||||
return e.e.saveUnify(term, e.rterm, e.bindings, e.rbindings, iter)
|
||||
}
|
||||
|
||||
func (e evalVirtualComplete) partialEvalSupportRule(rule *ast.Rule, path ast.Ref) (bool, error) {
|
||||
func (e evalVirtualComplete) partialEvalSupportRule(rule *ast.Rule, packagePath ast.Ref, ruleRef ast.Ref) (bool, error) {
|
||||
child := evalPool.Get()
|
||||
defer evalPool.Put(child)
|
||||
|
||||
e.e.child(rule.Body, child)
|
||||
e.e.childWithBindingSizeHint(rule.Body, child, ast.EstimateBodyBindingCount(rule.Body))
|
||||
child.traceEnter(rule)
|
||||
|
||||
e.e.saveStack.PushQuery(nil)
|
||||
@@ -3700,7 +3760,6 @@ func (e evalVirtualComplete) partialEvalSupportRule(rule *ast.Rule, path ast.Ref
|
||||
// Skip this rule body if it fails to type-check.
|
||||
// Type-checking failure means the rule body will never succeed.
|
||||
if e.e.compiler.PassesTypeCheck(plugged) {
|
||||
pkg, ruleRef := splitPackageAndRule(path)
|
||||
head := ast.RefHead(ruleRef, child.bindings.PlugNamespaced(rule.Head.Value, e.e.caller.bindings))
|
||||
|
||||
if !e.e.inliningControl.shallow {
|
||||
@@ -3710,7 +3769,7 @@ func (e evalVirtualComplete) partialEvalSupportRule(rule *ast.Rule, path ast.Ref
|
||||
plugged = applyCopyPropagation(cp, e.e.instr, plugged)
|
||||
}
|
||||
|
||||
e.e.saveSupport.InsertByPkg(pkg, &ast.Rule{
|
||||
e.e.saveSupport.InsertByPkg(packagePath, &ast.Rule{
|
||||
Head: head,
|
||||
Body: plugged,
|
||||
Default: rule.Default,
|
||||
@@ -3907,12 +3966,10 @@ func (e evalTerm) get(plugged *ast.Term) (*ast.Term, *bindings) {
|
||||
}
|
||||
|
||||
func (e evalTerm) save(iter unifyIterator) error {
|
||||
|
||||
v := e.e.generateVar(fmt.Sprintf("ref_%d", e.e.genvarid))
|
||||
e.e.genvarid++
|
||||
|
||||
return e.e.biunify(e.term, v, e.termbindings, e.bindings, func() error {
|
||||
|
||||
suffix := e.ref[e.pos:]
|
||||
ref := make(ast.Ref, len(suffix)+1)
|
||||
ref[0] = v
|
||||
@@ -3931,7 +3988,8 @@ type evalEvery struct {
|
||||
|
||||
func (e evalEvery) eval(iter unifyIterator) error {
|
||||
// unknowns in domain or body: save the expression, PE its body
|
||||
if e.e.unknown(e.Domain, e.e.bindings) || e.e.unknown(e.Body, e.e.bindings) {
|
||||
// partial() check to avoid e.Body -> Node boxing allocation
|
||||
if e.e.partial() && (e.e.unknown(e.Domain, e.e.bindings) || e.e.unknown(e.Body, e.e.bindings)) {
|
||||
return e.save(iter)
|
||||
}
|
||||
|
||||
@@ -3970,12 +4028,19 @@ func (e evalEvery) eval(iter unifyIterator) error {
|
||||
|
||||
child.closure(e.Body, body)
|
||||
body.findOne = true
|
||||
body.traceEnter(e.Body)
|
||||
|
||||
if e.e.traceEnabled {
|
||||
body.traceEnter(e.Body)
|
||||
}
|
||||
|
||||
done := false
|
||||
err := body.eval(func(*eval) error {
|
||||
body.traceExit(e.Body)
|
||||
if e.e.traceEnabled {
|
||||
body.traceExit(e.Body)
|
||||
body.traceRedo(e.Body)
|
||||
}
|
||||
done = true
|
||||
body.traceRedo(e.Body)
|
||||
|
||||
return nil
|
||||
})
|
||||
if !done {
|
||||
|
||||
+26
-30
@@ -90,11 +90,9 @@ var cacheableHTTPStatusCodes = [...]int{
|
||||
}
|
||||
|
||||
var (
|
||||
httpSendNetworkErrTerm = ast.StringTerm(HTTPSendNetworkErr)
|
||||
httpSendInternalErrTerm = ast.StringTerm(HTTPSendInternalErr)
|
||||
httpSendNetworkErrTerm, httpSendInternalErrTerm *ast.Term
|
||||
|
||||
allowedKeys = ast.NewSet()
|
||||
keyCache = make(map[string]*ast.Term, len(allowedKeyNames))
|
||||
cacheableCodes = ast.NewSet()
|
||||
requiredKeys = ast.NewSet(ast.InternedTerm("method"), ast.InternedTerm("url"))
|
||||
httpSendLatencyMetricKey = "rego_builtin_http_send"
|
||||
@@ -222,13 +220,13 @@ func getHTTPResponse(bctx BuiltinContext, req ast.Object) (*ast.Term, error) {
|
||||
func getKeyFromRequest(req ast.Object) (ast.Object, error) {
|
||||
// deep copy so changes to key do not reflect in the request object
|
||||
key := req.Copy()
|
||||
cacheIgnoredHeadersTerm := req.Get(keyCache["cache_ignored_headers"])
|
||||
allHeadersTerm := req.Get(ast.StringTerm("headers"))
|
||||
cacheIgnoredHeadersTerm := req.Get(ast.InternedTerm("cache_ignored_headers"))
|
||||
allHeadersTerm := req.Get(ast.InternedTerm("headers"))
|
||||
// skip because no headers to delete
|
||||
if cacheIgnoredHeadersTerm == nil || allHeadersTerm == nil {
|
||||
// need to explicitly set cache_ignored_headers to null
|
||||
// equivalent requests might have different sets of exclusion lists
|
||||
key.Insert(ast.StringTerm("cache_ignored_headers"), ast.InternedNullTerm)
|
||||
key.Insert(ast.InternedTerm("cache_ignored_headers"), ast.InternedNullTerm)
|
||||
return key, nil
|
||||
}
|
||||
var cacheIgnoredHeaders []string
|
||||
@@ -248,14 +246,22 @@ func getKeyFromRequest(req ast.Object) (ast.Object, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key.Insert(keyCache["headers"], ast.NewTerm(val))
|
||||
key.Insert(ast.InternedTerm("headers"), ast.NewTerm(val))
|
||||
// remove cache_ignored_headers key
|
||||
key.Insert(keyCache["cache_ignored_headers"], ast.InternedNullTerm)
|
||||
key.Insert(ast.InternedTerm("cache_ignored_headers"), ast.InternedNullTerm)
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
createKeys()
|
||||
for _, element := range allowedKeyNames {
|
||||
ast.InternStringTerm(element)
|
||||
allowedKeys.Add(ast.InternedTerm(element))
|
||||
}
|
||||
|
||||
ast.InternStringTerm(HTTPSendNetworkErr, HTTPSendInternalErr)
|
||||
httpSendNetworkErrTerm = ast.InternedTerm(HTTPSendNetworkErr)
|
||||
httpSendInternalErrTerm = ast.InternedTerm(HTTPSendInternalErr)
|
||||
|
||||
createCacheableHTTPStatusCodes()
|
||||
initDefaults()
|
||||
RegisterBuiltinFunc(ast.HTTPSend.Name, builtinHTTPSend)
|
||||
@@ -279,8 +285,7 @@ func handleHTTPSendErr(bctx BuiltinContext, err error) error {
|
||||
}
|
||||
|
||||
func initDefaults() {
|
||||
timeoutDuration := os.Getenv(defaultHTTPRequestTimeoutEnv)
|
||||
if timeoutDuration != "" {
|
||||
if timeoutDuration := os.Getenv(defaultHTTPRequestTimeoutEnv); timeoutDuration != "" {
|
||||
var err error
|
||||
defaultHTTPRequestTimeout, err = time.ParseDuration(timeoutDuration)
|
||||
if err != nil {
|
||||
@@ -714,7 +719,7 @@ func executeHTTPRequest(req *http.Request, client *http.Client, inputReqObj ast.
|
||||
var err error
|
||||
var retry int
|
||||
|
||||
retry, err = getNumberValFromReqObj(inputReqObj, keyCache["max_retry_attempts"])
|
||||
retry, err = getNumberValFromReqObj(inputReqObj, ast.InternedTerm("max_retry_attempts"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1008,15 +1013,6 @@ func insertIntoHTTPSendInterQueryCache(bctx BuiltinContext, key ast.Value, resp
|
||||
return nil
|
||||
}
|
||||
|
||||
func createKeys() {
|
||||
for _, element := range allowedKeyNames {
|
||||
term := ast.StringTerm(element)
|
||||
|
||||
allowedKeys.Add(term)
|
||||
keyCache[element] = term
|
||||
}
|
||||
}
|
||||
|
||||
func createCacheableHTTPStatusCodes() {
|
||||
for _, element := range cacheableHTTPStatusCodes {
|
||||
cacheableCodes.Add(ast.InternedTerm(element))
|
||||
@@ -1080,7 +1076,7 @@ func getNumberValFromReqObj(req ast.Object, key *ast.Term) (int, error) {
|
||||
}
|
||||
|
||||
func getCachingMode(req ast.Object) (cachingMode, error) {
|
||||
key := keyCache["caching_mode"]
|
||||
key := ast.InternedTerm("caching_mode")
|
||||
var s ast.String
|
||||
var ok bool
|
||||
if v := req.Get(key); v != nil {
|
||||
@@ -1480,11 +1476,11 @@ func (c *interQueryCache) CheckCache() (ast.Value, error) {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
c.forceJSONDecode, err = getBoolValFromReqObj(c.key, keyCache["force_json_decode"])
|
||||
c.forceJSONDecode, err = getBoolValFromReqObj(c.key, ast.InternedTerm("force_json_decode"))
|
||||
if err != nil {
|
||||
return nil, handleHTTPSendErr(c.bctx, err)
|
||||
}
|
||||
c.forceYAMLDecode, err = getBoolValFromReqObj(c.key, keyCache["force_yaml_decode"])
|
||||
c.forceYAMLDecode, err = getBoolValFromReqObj(c.key, ast.InternedTerm("force_yaml_decode"))
|
||||
if err != nil {
|
||||
return nil, handleHTTPSendErr(c.bctx, err)
|
||||
}
|
||||
@@ -1551,11 +1547,11 @@ func (c *intraQueryCache) CheckCache() (ast.Value, error) {
|
||||
|
||||
// InsertIntoCache inserts the key set on this object into the cache with the given value
|
||||
func (c *intraQueryCache) InsertIntoCache(value *http.Response) (ast.Value, error) {
|
||||
forceJSONDecode, err := getBoolValFromReqObj(c.key, keyCache["force_json_decode"])
|
||||
forceJSONDecode, err := getBoolValFromReqObj(c.key, ast.InternedTerm("force_json_decode"))
|
||||
if err != nil {
|
||||
return nil, handleHTTPSendErr(c.bctx, err)
|
||||
}
|
||||
forceYAMLDecode, err := getBoolValFromReqObj(c.key, keyCache["force_yaml_decode"])
|
||||
forceYAMLDecode, err := getBoolValFromReqObj(c.key, ast.InternedTerm("force_yaml_decode"))
|
||||
if err != nil {
|
||||
return nil, handleHTTPSendErr(c.bctx, err)
|
||||
}
|
||||
@@ -1590,12 +1586,12 @@ func (c *intraQueryCache) ExecuteHTTPRequest() (*http.Response, error) {
|
||||
}
|
||||
|
||||
func useInterQueryCache(req ast.Object) (bool, *forceCacheParams, error) {
|
||||
value, err := getBoolValFromReqObj(req, keyCache["cache"])
|
||||
value, err := getBoolValFromReqObj(req, ast.InternedTerm("cache"))
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
valueForceCache, err := getBoolValFromReqObj(req, keyCache["force_cache"])
|
||||
valueForceCache, err := getBoolValFromReqObj(req, ast.InternedTerm("force_cache"))
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
@@ -1613,7 +1609,7 @@ type forceCacheParams struct {
|
||||
}
|
||||
|
||||
func newForceCacheParams(req ast.Object) (*forceCacheParams, error) {
|
||||
term := req.Get(keyCache["force_cache_duration_seconds"])
|
||||
term := req.Get(ast.InternedTerm("force_cache_duration_seconds"))
|
||||
if term == nil {
|
||||
return nil, errors.New("'force_cache' set but 'force_cache_duration_seconds' parameter is missing")
|
||||
}
|
||||
@@ -1631,7 +1627,7 @@ func newForceCacheParams(req ast.Object) (*forceCacheParams, error) {
|
||||
func getRaiseErrorValue(req ast.Object) (bool, error) {
|
||||
result := ast.Boolean(true)
|
||||
var ok bool
|
||||
if v := req.Get(keyCache["raise_error"]); v != nil {
|
||||
if v := req.Get(ast.InternedTerm("raise_error")); v != nil {
|
||||
if result, ok = v.Value.(ast.Boolean); !ok {
|
||||
return false, errors.New("invalid value for raise_error field")
|
||||
}
|
||||
|
||||
+1
-2
@@ -1,5 +1,4 @@
|
||||
//go:build !go1.18 || !darwin
|
||||
// +build !go1.18 !darwin
|
||||
//go:build !darwin
|
||||
|
||||
package topdown
|
||||
|
||||
|
||||
-3
@@ -1,6 +1,3 @@
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
package topdown
|
||||
|
||||
func fixupDarwinGo118(x, y string) string {
|
||||
|
||||
+95
-118
@@ -17,8 +17,7 @@ import (
|
||||
|
||||
func builtinJSONRemove(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
// Expect an object and a string or array/set of strings
|
||||
_, err := builtins.ObjectOperand(operands[0].Value, 1)
|
||||
if err != nil {
|
||||
if _, err := builtins.ObjectOperand(operands[0].Value, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -110,7 +109,7 @@ func jsonRemove(a *ast.Term, b *ast.Term) (*ast.Term, error) {
|
||||
newArraySlice = append(newArraySlice, diffValue)
|
||||
}
|
||||
}
|
||||
return ast.NewTerm(ast.NewArray(newArraySlice...)), nil
|
||||
return ast.ArrayTerm(newArraySlice...), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid value type %T", a)
|
||||
}
|
||||
@@ -139,11 +138,10 @@ func builtinJSONFilter(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Te
|
||||
return iter(ast.NewTerm(r))
|
||||
}
|
||||
|
||||
func getJSONPaths(operand ast.Value) ([]ast.Ref, error) {
|
||||
var paths []ast.Ref
|
||||
|
||||
func getJSONPaths(operand ast.Value) (paths []ast.Ref, err error) {
|
||||
switch v := operand.(type) {
|
||||
case *ast.Array:
|
||||
paths = make([]ast.Ref, 0, v.Len())
|
||||
for i := range v.Len() {
|
||||
filter, err := parsePath(v.Elem(i))
|
||||
if err != nil {
|
||||
@@ -152,16 +150,13 @@ func getJSONPaths(operand ast.Value) ([]ast.Ref, error) {
|
||||
paths = append(paths, filter)
|
||||
}
|
||||
case ast.Set:
|
||||
err := v.Iter(func(f *ast.Term) error {
|
||||
filter, err := parsePath(f)
|
||||
paths = make([]ast.Ref, 0, v.Len())
|
||||
for _, item := range v.Slice() {
|
||||
filter, err := parsePath(item)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
paths = append(paths, filter)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, builtins.NewOperandTypeErr(2, v, "set", "array")
|
||||
@@ -170,6 +165,7 @@ func getJSONPaths(operand ast.Value) ([]ast.Ref, error) {
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
// parsePath parses a JSON pointer path or array of path segments into an ast.Ref.
|
||||
func parsePath(path *ast.Term) (ast.Ref, error) {
|
||||
// paths can either be a `/` separated json path or
|
||||
// an array or set of values
|
||||
@@ -177,19 +173,32 @@ func parsePath(path *ast.Term) (ast.Ref, error) {
|
||||
switch p := path.Value.(type) {
|
||||
case ast.String:
|
||||
if p == "" {
|
||||
return ast.Ref{}, nil
|
||||
return ast.InternedEmptyRefValue.(ast.Ref), nil
|
||||
}
|
||||
parts := strings.Split(strings.TrimLeft(string(p), "/"), "/")
|
||||
for _, part := range parts {
|
||||
part = strings.ReplaceAll(strings.ReplaceAll(part, "~1", "/"), "~0", "~")
|
||||
pathSegments = append(pathSegments, ast.StringTerm(part))
|
||||
|
||||
s := strings.TrimLeft(string(p), "/")
|
||||
n := strings.Count(s, "/") + 1
|
||||
|
||||
pathSegments = make(ast.Ref, 0, n)
|
||||
|
||||
part, remaining, found := strings.Cut(s, "/")
|
||||
unescaped := strings.ReplaceAll(strings.ReplaceAll(part, "~1", "/"), "~0", "~")
|
||||
pathSegments = append(pathSegments, ast.InternedTerm(unescaped))
|
||||
|
||||
for found {
|
||||
part, remaining, found = strings.Cut(remaining, "/")
|
||||
unescaped := strings.ReplaceAll(strings.ReplaceAll(part, "~1", "/"), "~0", "~")
|
||||
pathSegments = append(pathSegments, ast.InternedTerm(unescaped))
|
||||
}
|
||||
case *ast.Array:
|
||||
p.Foreach(func(term *ast.Term) {
|
||||
pathSegments = append(pathSegments, term)
|
||||
})
|
||||
pathSegments = make(ast.Ref, 0, p.Len())
|
||||
for i := range p.Len() {
|
||||
pathSegments = append(pathSegments, p.Elem(i))
|
||||
}
|
||||
default:
|
||||
return nil, builtins.NewOperandErr(2, "must be one of {set, array} containing string paths or array of path segments but got %v", ast.ValueName(p))
|
||||
return nil, builtins.NewOperandErr(2,
|
||||
"must be one of {set, array} containing string paths or array of path segments but got "+ast.ValueName(p),
|
||||
)
|
||||
}
|
||||
|
||||
return pathSegments, nil
|
||||
@@ -200,7 +209,7 @@ func pathsToObject(paths []ast.Ref) ast.Object {
|
||||
|
||||
for _, path := range paths {
|
||||
node := root
|
||||
var done bool
|
||||
done := false
|
||||
|
||||
// If the path is an empty JSON path, skip all further processing.
|
||||
if len(path) == 0 {
|
||||
@@ -209,7 +218,6 @@ func pathsToObject(paths []ast.Ref) ast.Object {
|
||||
|
||||
// Otherwise, we should have 1+ path segments to work with.
|
||||
for i := 0; i < len(path)-1 && !done; i++ {
|
||||
|
||||
k := path[i]
|
||||
child := node.Get(k)
|
||||
|
||||
@@ -238,106 +246,67 @@ func pathsToObject(paths []ast.Ref) ast.Object {
|
||||
return root
|
||||
}
|
||||
|
||||
type jsonPatch struct {
|
||||
op string
|
||||
path *ast.Term
|
||||
from *ast.Term
|
||||
value *ast.Term
|
||||
}
|
||||
|
||||
func getPatch(o ast.Object) (jsonPatch, error) {
|
||||
validOps := map[string]struct{}{"add": {}, "remove": {}, "replace": {}, "move": {}, "copy": {}, "test": {}}
|
||||
var out jsonPatch
|
||||
var ok bool
|
||||
getAttribute := func(attr string) (*ast.Term, error) {
|
||||
if term := o.Get(ast.StringTerm(attr)); term != nil {
|
||||
return term, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("missing '%s' attribute", attr)
|
||||
}
|
||||
|
||||
opTerm, err := getAttribute("op")
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
op, ok := opTerm.Value.(ast.String)
|
||||
if !ok {
|
||||
return out, errors.New("attribute 'op' must be a string")
|
||||
}
|
||||
out.op = string(op)
|
||||
if _, found := validOps[out.op]; !found {
|
||||
out.op = ""
|
||||
return out, fmt.Errorf("unrecognized op '%s'", string(op))
|
||||
}
|
||||
|
||||
pathTerm, err := getAttribute("path")
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
out.path = pathTerm
|
||||
|
||||
// Only fetch the "from" parameter for move/copy ops.
|
||||
switch out.op {
|
||||
case "move", "copy":
|
||||
fromTerm, err := getAttribute("from")
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
out.from = fromTerm
|
||||
}
|
||||
|
||||
// Only fetch the "value" parameter for add/replace/test ops.
|
||||
switch out.op {
|
||||
case "add", "replace", "test":
|
||||
valueTerm, err := getAttribute("value")
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
out.value = valueTerm
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func applyPatches(source *ast.Term, operations *ast.Array) (*ast.Term, error) {
|
||||
et := edittree.NewEditTree(source)
|
||||
et := edittree.EditTreeFromPool(source)
|
||||
defer edittree.Dispose(et)
|
||||
|
||||
for i := range operations.Len() {
|
||||
object, ok := operations.Elem(i).Value.(ast.Object)
|
||||
if !ok {
|
||||
return nil, errors.New("must be an array of JSON-Patch objects, but at least one element is not an object")
|
||||
}
|
||||
patch, err := getPatch(object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
// Validate
|
||||
if object.Get(ast.InternedTerm("path")) == nil {
|
||||
return nil, errors.New("missing required attribute 'path'")
|
||||
}
|
||||
path, err := parsePath(patch.path)
|
||||
|
||||
opTerm := object.Get(ast.InternedTerm("op"))
|
||||
if opTerm == nil {
|
||||
return nil, errors.New("missing required attribute 'op'")
|
||||
}
|
||||
|
||||
opStr, ok := opTerm.Value.(ast.String)
|
||||
if !ok {
|
||||
return nil, errors.New("attribute 'op' must be a string but found: " + ast.ValueName(opTerm.Value))
|
||||
}
|
||||
|
||||
path, err := parsePath(object.Get(ast.InternedTerm("path")))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch patch.op {
|
||||
switch string(opStr) {
|
||||
case "add":
|
||||
_, err = et.InsertAtPath(path, patch.value)
|
||||
if err != nil {
|
||||
value := object.Get(ast.InternedTerm("value"))
|
||||
if value == nil {
|
||||
return nil, errors.New("missing required attribute 'value'")
|
||||
}
|
||||
if _, err = et.InsertAtPath(path, value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "remove":
|
||||
_, err = et.DeleteAtPath(path)
|
||||
if err != nil {
|
||||
if _, err = et.DeleteAtPath(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "replace":
|
||||
_, err = et.DeleteAtPath(path)
|
||||
if err != nil {
|
||||
if _, err = et.DeleteAtPath(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = et.InsertAtPath(path, patch.value)
|
||||
if err != nil {
|
||||
value := object.Get(ast.InternedTerm("value"))
|
||||
if value == nil {
|
||||
return nil, errors.New("missing required attribute 'value'")
|
||||
}
|
||||
if _, err = et.InsertAtPath(path, value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "move":
|
||||
from, err := parsePath(patch.from)
|
||||
fromValue := object.Get(ast.InternedTerm("from"))
|
||||
if fromValue == nil {
|
||||
return nil, errors.New("missing required attribute 'from'")
|
||||
}
|
||||
|
||||
from, err := parsePath(fromValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -345,16 +314,18 @@ func applyPatches(source *ast.Term, operations *ast.Array) (*ast.Term, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = et.DeleteAtPath(from)
|
||||
if err != nil {
|
||||
if _, err = et.DeleteAtPath(from); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = et.InsertAtPath(path, chunk)
|
||||
if err != nil {
|
||||
if _, err = et.InsertAtPath(path, chunk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "copy":
|
||||
from, err := parsePath(patch.from)
|
||||
fromValue := object.Get(ast.InternedTerm("from"))
|
||||
if fromValue == nil {
|
||||
return nil, errors.New("missing required attribute 'from'")
|
||||
}
|
||||
from, err := parsePath(fromValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -362,8 +333,7 @@ func applyPatches(source *ast.Term, operations *ast.Array) (*ast.Term, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = et.InsertAtPath(path, chunk)
|
||||
if err != nil {
|
||||
if _, err = et.InsertAtPath(path, chunk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "test":
|
||||
@@ -371,34 +341,41 @@ func applyPatches(source *ast.Term, operations *ast.Array) (*ast.Term, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !chunk.Equal(patch.value) {
|
||||
return nil, fmt.Errorf("value from EditTree != patch value.\n\nExpected: %v\n\nFound: %v", patch.value, chunk)
|
||||
value := object.Get(ast.InternedTerm("value"))
|
||||
if value == nil {
|
||||
return nil, errors.New("missing required attribute 'value'")
|
||||
}
|
||||
if !chunk.Equal(value) {
|
||||
return nil, fmt.Errorf("value from EditTree != patch value.\n\nExpected: %v\n\nFound: %v", value, chunk)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unrecognized op: '%s'", string(opStr))
|
||||
}
|
||||
}
|
||||
final := et.Render()
|
||||
// TODO: Nil check here?
|
||||
return final, nil
|
||||
|
||||
return et.Render(), nil
|
||||
}
|
||||
|
||||
func builtinJSONPatch(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
// JSON patch supports arrays, objects as well as values as the target.
|
||||
target := ast.NewTerm(operands[0].Value)
|
||||
|
||||
// Expect an array of operations.
|
||||
operations, err := builtins.ArrayOperand(operands[1].Value, 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
patched, err := applyPatches(target, operations)
|
||||
// JSON patch supports arrays, objects as well as values as the target.
|
||||
patched, err := applyPatches(operands[0], operations)
|
||||
if err != nil {
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
return iter(patched)
|
||||
}
|
||||
|
||||
func init() {
|
||||
for _, key := range []string{"op", "path", "from", "value", "add", "remove", "replace", "move", "copy", "test"} {
|
||||
ast.InternStringTerm(key)
|
||||
}
|
||||
|
||||
RegisterBuiltinFunc(ast.JSONFilter.Name, builtinJSONFilter)
|
||||
RegisterBuiltinFunc(ast.JSONRemove.Name, builtinJSONRemove)
|
||||
RegisterBuiltinFunc(ast.JSONPatch.Name, builtinJSONPatch)
|
||||
|
||||
+28
-19
@@ -14,12 +14,7 @@ import (
|
||||
"github.com/open-policy-agent/opa/v1/topdown/builtins"
|
||||
)
|
||||
|
||||
var awsRequiredConfigKeyNames = ast.NewSet(
|
||||
ast.StringTerm("aws_service"),
|
||||
ast.StringTerm("aws_access_key"),
|
||||
ast.StringTerm("aws_secret_access_key"),
|
||||
ast.StringTerm("aws_region"),
|
||||
)
|
||||
var awsRequiredConfigKeyNames ast.Set
|
||||
|
||||
func stringFromTerm(t *ast.Term) string {
|
||||
if v, ok := t.Value.(ast.String); ok {
|
||||
@@ -103,7 +98,7 @@ func builtinAWSSigV4SignReq(_ BuiltinContext, operands []*ast.Term, iter func(*a
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
service := stringFromTerm(awsConfigObj.Get(ast.StringTerm("aws_service")))
|
||||
service := stringFromTerm(awsConfigObj.Get(ast.InternedTerm("aws_service")))
|
||||
awsCreds := aws.CredentialsFromObject(awsConfigObj)
|
||||
|
||||
// Timestamp for signing.
|
||||
@@ -130,11 +125,12 @@ func builtinAWSSigV4SignReq(_ BuiltinContext, operands []*ast.Term, iter func(*a
|
||||
// Prepare required fields from the HTTP request object.
|
||||
var theURL *url.URL
|
||||
var method string
|
||||
reqURL := reqObj.Get(ast.StringTerm("url"))
|
||||
reqMethod := reqObj.Get(ast.StringTerm("method"))
|
||||
reqURL := reqObj.Get(ast.InternedTerm("url"))
|
||||
keyMethod := ast.InternedTerm("method")
|
||||
reqMethod := reqObj.Get(keyMethod)
|
||||
|
||||
headers := ast.NewObject()
|
||||
headersTerm := reqObj.Get(ast.StringTerm("headers"))
|
||||
headersTerm := reqObj.Get(ast.InternedTerm("headers"))
|
||||
if headersTerm != nil {
|
||||
var ok bool
|
||||
headers, ok = headersTerm.Value.(ast.Object)
|
||||
@@ -146,10 +142,10 @@ func builtinAWSSigV4SignReq(_ BuiltinContext, operands []*ast.Term, iter func(*a
|
||||
// Check types on the request parameters.
|
||||
invalidParameters := ast.NewSet()
|
||||
if _, ok := reqURL.Value.(ast.String); !ok {
|
||||
invalidParameters.Add(ast.StringTerm("url"))
|
||||
invalidParameters.Add(ast.InternedTerm("url"))
|
||||
}
|
||||
if _, ok := reqMethod.Value.(ast.String); !ok {
|
||||
invalidParameters.Add(ast.StringTerm("method"))
|
||||
invalidParameters.Add(keyMethod)
|
||||
}
|
||||
if invalidParameters.Len() > 0 {
|
||||
return builtins.NewOperandErr(1, "invalid values for required request parameters(s): %v", invalidParameters)
|
||||
@@ -161,8 +157,8 @@ func builtinAWSSigV4SignReq(_ BuiltinContext, operands []*ast.Term, iter func(*a
|
||||
}
|
||||
method = stringFromTerm(reqMethod)
|
||||
|
||||
bodyTerm := reqObj.Get(ast.StringTerm("body"))
|
||||
rawBodyTerm := reqObj.Get(ast.StringTerm("raw_body"))
|
||||
bodyTerm := reqObj.Get(ast.InternedTerm("body"))
|
||||
rawBodyTerm := reqObj.Get(ast.InternedTerm("raw_body"))
|
||||
body, err := getReqBodyBytes(bodyTerm, rawBodyTerm)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -173,7 +169,7 @@ func builtinAWSSigV4SignReq(_ BuiltinContext, operands []*ast.Term, iter func(*a
|
||||
|
||||
// if payload signing config is set, pass it down to the signing method
|
||||
disablePayloadSigning := false
|
||||
t := awsConfigObj.Get(ast.StringTerm("disable_payload_signing"))
|
||||
t := awsConfigObj.Get(ast.InternedTerm("disable_payload_signing"))
|
||||
if t != nil {
|
||||
if v, ok := t.Value.(ast.Boolean); ok {
|
||||
disablePayloadSigning = bool(v)
|
||||
@@ -188,24 +184,37 @@ func builtinAWSSigV4SignReq(_ BuiltinContext, operands []*ast.Term, iter func(*a
|
||||
for k, v := range headersMap {
|
||||
// objectToMap doesn't support arrays
|
||||
if len(v) == 1 {
|
||||
signedHeadersObj.Insert(ast.StringTerm(k), ast.StringTerm(v[0]))
|
||||
signedHeadersObj.Insert(ast.InternedTerm(k), ast.StringTerm(v[0]))
|
||||
}
|
||||
}
|
||||
// Set authorization header
|
||||
signedHeadersObj.Insert(ast.StringTerm("Authorization"), ast.StringTerm(authHeader))
|
||||
signedHeadersObj.Insert(ast.InternedTerm("Authorization"), ast.StringTerm(authHeader))
|
||||
|
||||
// set aws signature headers
|
||||
for k, v := range awsHeadersMap {
|
||||
signedHeadersObj.Insert(ast.StringTerm(k), ast.StringTerm(v))
|
||||
signedHeadersObj.Insert(ast.InternedTerm(k), ast.StringTerm(v))
|
||||
}
|
||||
|
||||
// Create new request object with updated headers.
|
||||
out := reqObj.Copy()
|
||||
out.Insert(ast.StringTerm("headers"), ast.NewTerm(signedHeadersObj))
|
||||
out.Insert(ast.InternedTerm("headers"), ast.NewTerm(signedHeadersObj))
|
||||
|
||||
return iter(ast.NewTerm(out))
|
||||
}
|
||||
|
||||
func init() {
|
||||
for _, key := range []string{
|
||||
"aws_service", "aws_access_key", "aws_secret_access_key", "aws_region", "disable_payload_signing",
|
||||
} {
|
||||
ast.InternStringTerm(key)
|
||||
}
|
||||
|
||||
awsRequiredConfigKeyNames = ast.NewSet(
|
||||
ast.InternedTerm("aws_service"),
|
||||
ast.InternedTerm("aws_access_key"),
|
||||
ast.InternedTerm("aws_secret_access_key"),
|
||||
ast.InternedTerm("aws_region"),
|
||||
)
|
||||
|
||||
RegisterBuiltinFunc(ast.ProvidersAWSSignReqObj.Name, builtinAWSSigV4SignReq)
|
||||
}
|
||||
|
||||
+6
-8
@@ -8,7 +8,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
@@ -573,30 +572,29 @@ func builtinReplaceN(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.T
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keys := patterns.Keys()
|
||||
sort.Slice(keys, func(i, j int) bool { return ast.Compare(keys[i].Value, keys[j].Value) < 0 })
|
||||
|
||||
s, err := builtins.StringOperand(operands[1].Value, 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oldnewArr := make([]string, 0, len(keys)*2)
|
||||
keys := util.SortedFunc(patterns.Keys(), ast.TermValueCompare)
|
||||
pairs := make([]string, 0, len(keys)*2)
|
||||
|
||||
for _, k := range keys {
|
||||
keyVal, ok := k.Value.(ast.String)
|
||||
if !ok {
|
||||
return builtins.NewOperandErr(1, "non-string key found in pattern object")
|
||||
}
|
||||
val := patterns.Get(k) // cannot be nil
|
||||
strVal, ok := val.Value.(ast.String)
|
||||
strVal, ok := patterns.Get(k).Value.(ast.String)
|
||||
if !ok {
|
||||
return builtins.NewOperandErr(1, "non-string value found in pattern object")
|
||||
}
|
||||
oldnewArr = append(oldnewArr, string(keyVal), string(strVal))
|
||||
pairs = append(pairs, string(keyVal), string(strVal))
|
||||
}
|
||||
|
||||
sink := newSink(ast.ReplaceN.Name, len(s), bctx.Cancel)
|
||||
replacer := strings.NewReplacer(oldnewArr...)
|
||||
replacer := strings.NewReplacer(pairs...)
|
||||
if _, err := replacer.WriteString(sink, string(s)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+5
-1
@@ -1290,7 +1290,11 @@ func createTokenCacheKey(serializedJwt ast.Value, publicKey ast.Value) ast.Value
|
||||
|
||||
func init() {
|
||||
// By default, the JWT cache is disabled.
|
||||
cache.RegisterDefaultInterQueryBuiltinValueCacheConfig(tokenCacheName, nil)
|
||||
disabled := true
|
||||
var tokenCache = cache.NamedValueCacheConfig{
|
||||
Disabled: &disabled,
|
||||
}
|
||||
cache.RegisterDefaultInterQueryBuiltinValueCacheConfig(tokenCacheName, &tokenCache)
|
||||
|
||||
RegisterBuiltinFunc(ast.JWTDecode.Name, builtinJWTDecode)
|
||||
RegisterBuiltinFunc(ast.JWTVerifyRS256.Name, builtinJWTVerifyRS256)
|
||||
|
||||
+12
-11
@@ -25,21 +25,22 @@ func evalWalk(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error
|
||||
|
||||
func walk(filter, path *ast.Array, input *ast.Term, iter func(*ast.Term) error) error {
|
||||
if filter == nil || filter.Len() == 0 {
|
||||
var pathCopy *ast.Array
|
||||
if path == nil {
|
||||
pathCopy = ast.InternedEmptyArrayValue
|
||||
if err := iter(ast.ArrayTerm(ast.NewTerm(ast.InternedEmptyArrayValue), input)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Shallow copy, as while the array is modified, the elements are not
|
||||
pathCopy = copyShallow(path)
|
||||
}
|
||||
pathCopy := copyShallow(path)
|
||||
|
||||
// TODO(ae): I'd *really* like these terms to be retrieved from a sync.Pool, and
|
||||
// returned after iter is called. However, all my atttempts to do this have failed
|
||||
// as there seems to be something holding on to these references after the call,
|
||||
// leading to modifications that entirely alter the results. Perhaps this is not
|
||||
// possible to do, but if it is,it would be a huge performance win.
|
||||
if err := iter(ast.ArrayTerm(ast.NewTerm(pathCopy), input)); err != nil {
|
||||
return err
|
||||
// TODO(ae): I'd *really* like these terms to be retrieved from a sync.Pool, and
|
||||
// returned after iter is called. However, all my atttempts to do this have failed
|
||||
// as there seems to be something holding on to these references after the call,
|
||||
// leading to modifications that entirely alter the results. Perhaps this is not
|
||||
// possible to do, but if it is,it would be a huge performance win.
|
||||
if err := iter(ast.ArrayTerm(ast.NewTerm(pathCopy), input)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -219,7 +219,7 @@ func (t *Array) toMap() map[string]any {
|
||||
|
||||
func (t *Array) String() string {
|
||||
prefix := "array"
|
||||
buf := []string{}
|
||||
buf := make([]string, 0, len(t.static))
|
||||
for _, tpe := range t.static {
|
||||
buf = append(buf, Sprint(tpe))
|
||||
}
|
||||
|
||||
+6
-1
@@ -161,7 +161,6 @@ func (sp *SlicePool[T]) Get(length int) *[]T {
|
||||
clear(d)
|
||||
|
||||
*s = d
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -171,3 +170,9 @@ func (sp *SlicePool[T]) Put(s *[]T) {
|
||||
sp.pool.Put(s)
|
||||
}
|
||||
}
|
||||
|
||||
// SortedFunc is simply a shorthand for [slices.SortFunc] which also returns the sorted slice.
|
||||
func SortedFunc[T any, S ~[]T](s S, cmp func(a, b T) int) S {
|
||||
slices.SortFunc(s, cmp)
|
||||
return s
|
||||
}
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import (
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
var Version = "1.13.2"
|
||||
var Version = "1.14.1"
|
||||
|
||||
// GoVersion is the version of Go this was built with
|
||||
var GoVersion = runtime.Version()
|
||||
|
||||
+7
-3
@@ -1,11 +1,15 @@
|
||||
package ast
|
||||
|
||||
func arg2map(defs ArgumentDefinitionList, args ArgumentList, vars map[string]interface{}) map[string]interface{} {
|
||||
result := map[string]interface{}{}
|
||||
func arg2map(
|
||||
defs ArgumentDefinitionList,
|
||||
args ArgumentList,
|
||||
vars map[string]any,
|
||||
) map[string]any {
|
||||
result := map[string]any{}
|
||||
var err error
|
||||
|
||||
for _, argDef := range defs {
|
||||
var val interface{}
|
||||
var val any
|
||||
var hasValue bool
|
||||
|
||||
if argValue := args.ForName(argDef.Name); argValue != nil {
|
||||
|
||||
+3
-6
@@ -1,5 +1,7 @@
|
||||
package ast
|
||||
|
||||
import "slices"
|
||||
|
||||
type DefinitionKind string
|
||||
|
||||
const (
|
||||
@@ -54,12 +56,7 @@ func (d *Definition) IsInputType() bool {
|
||||
}
|
||||
|
||||
func (d *Definition) OneOf(types ...string) bool {
|
||||
for _, t := range types {
|
||||
if d.Name == t {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.Contains(types, d.Name)
|
||||
}
|
||||
|
||||
type FieldDefinition struct {
|
||||
|
||||
+3
-3
@@ -3,7 +3,7 @@ package ast
|
||||
type DirectiveLocation string
|
||||
|
||||
const (
|
||||
// Executable
|
||||
// Executable.
|
||||
LocationQuery DirectiveLocation = `QUERY`
|
||||
LocationMutation DirectiveLocation = `MUTATION`
|
||||
LocationSubscription DirectiveLocation = `SUBSCRIPTION`
|
||||
@@ -12,7 +12,7 @@ const (
|
||||
LocationFragmentSpread DirectiveLocation = `FRAGMENT_SPREAD`
|
||||
LocationInlineFragment DirectiveLocation = `INLINE_FRAGMENT`
|
||||
|
||||
// Type System
|
||||
// Type System.
|
||||
LocationSchema DirectiveLocation = `SCHEMA`
|
||||
LocationScalar DirectiveLocation = `SCALAR`
|
||||
LocationObject DirectiveLocation = `OBJECT`
|
||||
@@ -38,7 +38,7 @@ type Directive struct {
|
||||
Location DirectiveLocation
|
||||
}
|
||||
|
||||
func (d *Directive) ArgumentMap(vars map[string]interface{}) map[string]interface{} {
|
||||
func (d *Directive) ArgumentMap(vars map[string]any) map[string]any {
|
||||
if d.Definition == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
+4
-3
@@ -42,7 +42,7 @@ type Schema struct {
|
||||
Comment *CommentGroup
|
||||
}
|
||||
|
||||
// AddTypes is the helper to add types definition to the schema
|
||||
// AddTypes is the helper to add types definition to the schema.
|
||||
func (s *Schema) AddTypes(defs ...*Definition) {
|
||||
if s.Types == nil {
|
||||
s.Types = make(map[string]*Definition)
|
||||
@@ -56,7 +56,7 @@ func (s *Schema) AddPossibleType(name string, def *Definition) {
|
||||
s.PossibleTypes[name] = append(s.PossibleTypes[name], def)
|
||||
}
|
||||
|
||||
// GetPossibleTypes will enumerate all the definitions for a given interface or union
|
||||
// GetPossibleTypes will enumerate all the definitions for a given interface or union.
|
||||
func (s *Schema) GetPossibleTypes(def *Definition) []*Definition {
|
||||
return s.PossibleTypes[def.Name]
|
||||
}
|
||||
@@ -65,7 +65,8 @@ func (s *Schema) AddImplements(name string, iface *Definition) {
|
||||
s.Implements[name] = append(s.Implements[name], iface)
|
||||
}
|
||||
|
||||
// GetImplements returns all the interface and union definitions that the given definition satisfies
|
||||
// GetImplements returns all the interface and union definitions that the given definition
|
||||
// satisfies.
|
||||
func (s *Schema) GetImplements(def *Definition) []*Definition {
|
||||
return s.Implements[def.Name]
|
||||
}
|
||||
|
||||
+8
-6
@@ -8,8 +8,8 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Dump turns ast into a stable string format for assertions in tests
|
||||
func Dump(i interface{}) string {
|
||||
// Dump turns ast into a stable string format for assertions in tests.
|
||||
func Dump(i any) string {
|
||||
v := reflect.ValueOf(i)
|
||||
|
||||
d := dumper{Buffer: &bytes.Buffer{}}
|
||||
@@ -126,7 +126,6 @@ func isZero(v reflect.Value) bool {
|
||||
return v.IsNil()
|
||||
case reflect.Func, reflect.Map:
|
||||
return v.IsNil()
|
||||
|
||||
case reflect.Array, reflect.Slice:
|
||||
if v.IsNil() {
|
||||
return true
|
||||
@@ -144,10 +143,13 @@ func isZero(v reflect.Value) bool {
|
||||
return z
|
||||
case reflect.String:
|
||||
return v.String() == ""
|
||||
case reflect.Bool:
|
||||
// Never consider Bool field as zero value.
|
||||
// Always include them in AST dump.
|
||||
return false
|
||||
default:
|
||||
return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())
|
||||
}
|
||||
|
||||
// Compare other types directly:
|
||||
return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()))
|
||||
}
|
||||
|
||||
func (d *dumper) dumpPtr(v reflect.Value) {
|
||||
|
||||
+2
-2
@@ -27,7 +27,7 @@ func (path Path) String() string {
|
||||
for i, v := range path {
|
||||
switch v := v.(type) {
|
||||
case PathIndex:
|
||||
str.WriteString(fmt.Sprintf("[%d]", v))
|
||||
fmt.Fprintf(&str, "[%d]", v)
|
||||
case PathName:
|
||||
if i != 0 {
|
||||
str.WriteByte('.')
|
||||
@@ -41,7 +41,7 @@ func (path Path) String() string {
|
||||
}
|
||||
|
||||
func (path *Path) UnmarshalJSON(b []byte) error {
|
||||
var vs []interface{}
|
||||
var vs []any
|
||||
err := json.Unmarshal(b, &vs)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ type Argument struct {
|
||||
Comment *CommentGroup
|
||||
}
|
||||
|
||||
func (f *Field) ArgumentMap(vars map[string]interface{}) map[string]interface{} {
|
||||
func (f *Field) ArgumentMap(vars map[string]any) map[string]any {
|
||||
if f.Definition == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package ast
|
||||
|
||||
// Source covers a single *.graphql file
|
||||
// Source covers a single *.graphql file.
|
||||
type Source struct {
|
||||
// Name is the filename of the source
|
||||
Name string
|
||||
|
||||
+3
-3
@@ -42,7 +42,7 @@ type ChildValue struct {
|
||||
Comment *CommentGroup
|
||||
}
|
||||
|
||||
func (v *Value) Value(vars map[string]interface{}) (interface{}, error) {
|
||||
func (v *Value) Value(vars map[string]any) (any, error) {
|
||||
if v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -66,7 +66,7 @@ func (v *Value) Value(vars map[string]interface{}) (interface{}, error) {
|
||||
case NullValue:
|
||||
return nil, nil
|
||||
case ListValue:
|
||||
var val []interface{}
|
||||
var val []any
|
||||
for _, elem := range v.Children {
|
||||
elemVal, err := elem.Value.Value(vars)
|
||||
if err != nil {
|
||||
@@ -76,7 +76,7 @@ func (v *Value) Value(vars map[string]interface{}) (interface{}, error) {
|
||||
}
|
||||
return val, nil
|
||||
case ObjectValue:
|
||||
val := map[string]interface{}{}
|
||||
val := map[string]any{}
|
||||
for _, elem := range v.Children {
|
||||
elemVal, err := elem.Value.Value(vars)
|
||||
if err != nil {
|
||||
|
||||
+16
-15
@@ -11,12 +11,12 @@ import (
|
||||
|
||||
// Error is the standard graphql error type described in https://spec.graphql.org/draft/#sec-Errors
|
||||
type Error struct {
|
||||
Err error `json:"-"`
|
||||
Message string `json:"message"`
|
||||
Path ast.Path `json:"path,omitempty"`
|
||||
Locations []Location `json:"locations,omitempty"`
|
||||
Extensions map[string]interface{} `json:"extensions,omitempty"`
|
||||
Rule string `json:"-"`
|
||||
Err error `json:"-"`
|
||||
Message string `json:"message"`
|
||||
Path ast.Path `json:"path,omitempty"`
|
||||
Locations []Location `json:"locations,omitempty"`
|
||||
Extensions map[string]any `json:"extensions,omitempty"`
|
||||
Rule string `json:"-"`
|
||||
}
|
||||
|
||||
func (err *Error) SetFile(file string) {
|
||||
@@ -24,7 +24,7 @@ func (err *Error) SetFile(file string) {
|
||||
return
|
||||
}
|
||||
if err.Extensions == nil {
|
||||
err.Extensions = map[string]interface{}{}
|
||||
err.Extensions = map[string]any{}
|
||||
}
|
||||
|
||||
err.Extensions["file"] = file
|
||||
@@ -99,7 +99,7 @@ func (errs List) Is(target error) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (errs List) As(target interface{}) bool {
|
||||
func (errs List) As(target any) bool {
|
||||
for _, err := range errs {
|
||||
if errors.As(err, target) {
|
||||
return true
|
||||
@@ -141,7 +141,8 @@ func WrapIfUnwrapped(err error) *Error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if gqlErr, ok := err.(*Error); ok {
|
||||
gqlErr := &Error{}
|
||||
if errors.As(err, &gqlErr) {
|
||||
return gqlErr
|
||||
}
|
||||
return &Error{
|
||||
@@ -150,20 +151,20 @@ func WrapIfUnwrapped(err error) *Error {
|
||||
}
|
||||
}
|
||||
|
||||
func Errorf(message string, args ...interface{}) *Error {
|
||||
func Errorf(message string, args ...any) *Error {
|
||||
return &Error{
|
||||
Message: fmt.Sprintf(message, args...),
|
||||
}
|
||||
}
|
||||
|
||||
func ErrorPathf(path ast.Path, message string, args ...interface{}) *Error {
|
||||
func ErrorPathf(path ast.Path, message string, args ...any) *Error {
|
||||
return &Error{
|
||||
Message: fmt.Sprintf(message, args...),
|
||||
Path: path,
|
||||
}
|
||||
}
|
||||
|
||||
func ErrorPosf(pos *ast.Position, message string, args ...interface{}) *Error {
|
||||
func ErrorPosf(pos *ast.Position, message string, args ...any) *Error {
|
||||
if pos == nil {
|
||||
return ErrorLocf(
|
||||
"",
|
||||
@@ -182,10 +183,10 @@ func ErrorPosf(pos *ast.Position, message string, args ...interface{}) *Error {
|
||||
)
|
||||
}
|
||||
|
||||
func ErrorLocf(file string, line int, col int, message string, args ...interface{}) *Error {
|
||||
var extensions map[string]interface{}
|
||||
func ErrorLocf(file string, line, col int, message string, args ...any) *Error {
|
||||
var extensions map[string]any
|
||||
if file != "" {
|
||||
extensions = map[string]interface{}{"file": file}
|
||||
extensions = map[string]any{"file": file}
|
||||
}
|
||||
return &Error{
|
||||
Message: fmt.Sprintf(message, args...),
|
||||
|
||||
+79
-22
@@ -2,13 +2,14 @@ package lexer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"slices"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
)
|
||||
|
||||
// Lexer turns graphql request and schema strings into tokens
|
||||
// Lexer turns graphql request and schema strings into tokens.
|
||||
type Lexer struct {
|
||||
*ast.Source
|
||||
// An offset into the string in bytes
|
||||
@@ -32,7 +33,7 @@ func New(src *ast.Source) Lexer {
|
||||
}
|
||||
}
|
||||
|
||||
// take one rune from input and advance end
|
||||
// take one rune from input and advance end.
|
||||
func (s *Lexer) peek() (rune, int) {
|
||||
return utf8.DecodeRuneInString(s.Input[s.end:])
|
||||
}
|
||||
@@ -55,7 +56,7 @@ func (s *Lexer) makeValueToken(kind Type, value string) (Token, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Lexer) makeError(format string, args ...interface{}) (Token, *gqlerror.Error) {
|
||||
func (s *Lexer) makeError(format string, args ...any) (Token, *gqlerror.Error) {
|
||||
column := s.endRunes - s.lineStartRunes + 1
|
||||
return Token{
|
||||
Kind: Invalid,
|
||||
@@ -122,7 +123,59 @@ func (s *Lexer) ReadToken() (Token, error) {
|
||||
case '#':
|
||||
return s.readComment()
|
||||
|
||||
case '_', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z':
|
||||
case '_',
|
||||
'a',
|
||||
'b',
|
||||
'c',
|
||||
'd',
|
||||
'e',
|
||||
'f',
|
||||
'g',
|
||||
'h',
|
||||
'i',
|
||||
'j',
|
||||
'k',
|
||||
'l',
|
||||
'm',
|
||||
'n',
|
||||
'o',
|
||||
'p',
|
||||
'q',
|
||||
'r',
|
||||
's',
|
||||
't',
|
||||
'u',
|
||||
'v',
|
||||
'w',
|
||||
'x',
|
||||
'y',
|
||||
'z',
|
||||
'A',
|
||||
'B',
|
||||
'C',
|
||||
'D',
|
||||
'E',
|
||||
'F',
|
||||
'G',
|
||||
'H',
|
||||
'I',
|
||||
'J',
|
||||
'K',
|
||||
'L',
|
||||
'M',
|
||||
'N',
|
||||
'O',
|
||||
'P',
|
||||
'Q',
|
||||
'R',
|
||||
'S',
|
||||
'T',
|
||||
'U',
|
||||
'V',
|
||||
'W',
|
||||
'X',
|
||||
'Y',
|
||||
'Z':
|
||||
return s.readName()
|
||||
|
||||
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
@@ -144,14 +197,16 @@ func (s *Lexer) ReadToken() (Token, error) {
|
||||
}
|
||||
|
||||
if r == '\'' {
|
||||
return s.makeError(`Unexpected single quote character ('), did you mean to use a double quote (")?`)
|
||||
return s.makeError(
|
||||
`Unexpected single quote character ('), did you mean to use a double quote (")?`,
|
||||
)
|
||||
}
|
||||
|
||||
return s.makeError(`Cannot parse the unexpected character "%s".`, string(r))
|
||||
}
|
||||
|
||||
// ws reads from body starting at startPosition until it finds a non-whitespace
|
||||
// or commented character, and updates the token end to include all whitespace
|
||||
// or commented character, and updates the token end to include all whitespace.
|
||||
func (s *Lexer) ws() {
|
||||
for s.end < len(s.Input) {
|
||||
switch s.Input[s.end] {
|
||||
@@ -189,7 +244,7 @@ func (s *Lexer) ws() {
|
||||
|
||||
// readComment from the input
|
||||
//
|
||||
// #[\u0009\u0020-\uFFFF]*
|
||||
// #[\u0009\u0020-\uFFFF]*.
|
||||
func (s *Lexer) readComment() (Token, error) {
|
||||
for s.end < len(s.Input) {
|
||||
r, w := s.peek()
|
||||
@@ -256,23 +311,21 @@ func (s *Lexer) readNumber() (Token, error) {
|
||||
return s.makeToken(Int)
|
||||
}
|
||||
|
||||
// acceptByte if it matches any of given bytes, returning true if it found anything
|
||||
// acceptByte if it matches any of given bytes, returning true if it found anything.
|
||||
func (s *Lexer) acceptByte(bytes ...uint8) bool {
|
||||
if s.end >= len(s.Input) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, accepted := range bytes {
|
||||
if s.Input[s.end] == accepted {
|
||||
s.end++
|
||||
s.endRunes++
|
||||
return true
|
||||
}
|
||||
if slices.Contains(bytes, s.Input[s.end]) {
|
||||
s.end++
|
||||
s.endRunes++
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// acceptDigits from the input, returning the number of digits it found
|
||||
// acceptDigits from the input, returning the number of digits it found.
|
||||
func (s *Lexer) acceptDigits() int {
|
||||
consumed := 0
|
||||
for s.end < len(s.Input) && s.Input[s.end] >= '0' && s.Input[s.end] <= '9' {
|
||||
@@ -285,7 +338,7 @@ func (s *Lexer) acceptDigits() int {
|
||||
}
|
||||
|
||||
// describeNext peeks at the input and returns a human readable string. This should will alloc
|
||||
// and should only be used in errors
|
||||
// and should only be used in errors.
|
||||
func (s *Lexer) describeNext() string {
|
||||
if s.end < len(s.Input) {
|
||||
return `"` + string(s.Input[s.end]) + `"`
|
||||
@@ -295,7 +348,7 @@ func (s *Lexer) describeNext() string {
|
||||
|
||||
// readString from the input
|
||||
//
|
||||
// "([^"\\\u000A\u000D]|(\\(u[0-9a-fA-F]{4}|["\\/bfnrt])))*"
|
||||
// "([^"\\\u000A\u000D]|(\\(u[0-9a-fA-F]{4}|["\\/bfnrt])))*".
|
||||
func (s *Lexer) readString() (Token, error) {
|
||||
inputLen := len(s.Input)
|
||||
|
||||
@@ -332,7 +385,8 @@ func (s *Lexer) readString() (Token, error) {
|
||||
|
||||
case '"':
|
||||
t, err := s.makeToken(String)
|
||||
// the token should not include the quotes in its value, but should cover them in its position
|
||||
// the token should not include the quotes in its value, but should cover them in its
|
||||
// position
|
||||
t.Pos.Start--
|
||||
t.Pos.End++
|
||||
|
||||
@@ -370,7 +424,10 @@ func (s *Lexer) readString() (Token, error) {
|
||||
if !ok {
|
||||
s.end++
|
||||
s.endRunes++
|
||||
return s.makeError("Invalid character escape sequence: \\%s.", s.Input[s.end:s.end+5])
|
||||
return s.makeError(
|
||||
"Invalid character escape sequence: \\%s.",
|
||||
s.Input[s.end:s.end+5],
|
||||
)
|
||||
}
|
||||
buf.WriteRune(r)
|
||||
s.end += 6
|
||||
@@ -405,7 +462,7 @@ func (s *Lexer) readString() (Token, error) {
|
||||
|
||||
// readBlockString from the input
|
||||
//
|
||||
// """("?"?(\\"""|\\(?!=""")|[^"\\]))*"""
|
||||
// """("?"?(\\"""|\\(?!=""")|[^"\\]))*""".
|
||||
func (s *Lexer) readBlockString() (Token, error) {
|
||||
inputLen := len(s.Input)
|
||||
|
||||
@@ -433,7 +490,7 @@ func (s *Lexer) readBlockString() (Token, error) {
|
||||
// If we have at least 3 quotes, use the last 3 as the closing quote
|
||||
if quoteCount >= 3 {
|
||||
// Add any extra quotes to the buffer (except the last 3)
|
||||
for j := 0; j < quoteCount-3; j++ {
|
||||
for range quoteCount - 3 {
|
||||
buf.WriteByte('"')
|
||||
}
|
||||
|
||||
@@ -508,7 +565,7 @@ func unhex(b string) (v rune, ok bool) {
|
||||
|
||||
// readName from the input
|
||||
//
|
||||
// [_A-Za-z][_0-9A-Za-z]*
|
||||
// [_A-Za-z][_0-9A-Za-z]*.
|
||||
func (s *Lexer) readName() (Token, error) {
|
||||
for s.end < len(s.Input) {
|
||||
r, w := s.peek()
|
||||
|
||||
+3
-3
@@ -91,7 +91,7 @@ func (p *parser) peek() lexer.Token {
|
||||
return p.peekToken
|
||||
}
|
||||
|
||||
func (p *parser) error(tok lexer.Token, format string, args ...interface{}) {
|
||||
func (p *parser) error(tok lexer.Token, format string, args ...any) {
|
||||
if p.err != nil {
|
||||
return
|
||||
}
|
||||
@@ -165,7 +165,7 @@ func (p *parser) unexpectedToken(tok lexer.Token) {
|
||||
p.error(tok, "Unexpected %s", tok.String())
|
||||
}
|
||||
|
||||
func (p *parser) many(start lexer.Type, end lexer.Type, cb func()) {
|
||||
func (p *parser) many(start, end lexer.Type, cb func()) {
|
||||
hasDef := p.skip(start)
|
||||
if !hasDef {
|
||||
return
|
||||
@@ -177,7 +177,7 @@ func (p *parser) many(start lexer.Type, end lexer.Type, cb func()) {
|
||||
p.next()
|
||||
}
|
||||
|
||||
func (p *parser) some(start lexer.Type, end lexer.Type, cb func()) *ast.CommentGroup {
|
||||
func (p *parser) some(start, end lexer.Type, cb func()) *ast.CommentGroup {
|
||||
hasDef := p.skip(start)
|
||||
if !hasDef {
|
||||
return nil
|
||||
|
||||
+7
-3
@@ -1,9 +1,8 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"github.com/vektah/gqlparser/v2/lexer"
|
||||
|
||||
. "github.com/vektah/gqlparser/v2/ast" //nolint:staticcheck // bad, yeah
|
||||
"github.com/vektah/gqlparser/v2/lexer"
|
||||
)
|
||||
|
||||
func ParseQuery(source *Source) (*QueryDocument, error) {
|
||||
@@ -259,7 +258,12 @@ func (p *parser) parseValueLiteral(isConst bool) *Value {
|
||||
p.unexpectedError()
|
||||
return nil
|
||||
}
|
||||
return &Value{Position: &token.Pos, Comment: p.comment, Raw: p.parseVariable(), Kind: Variable}
|
||||
return &Value{
|
||||
Position: &token.Pos,
|
||||
Comment: p.comment,
|
||||
Raw: p.parseVariable(),
|
||||
Kind: Variable,
|
||||
}
|
||||
case lexer.Int:
|
||||
kind = IntValue
|
||||
case lexer.Float:
|
||||
|
||||
+8
-11
@@ -8,11 +8,12 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/agnivade/levenshtein"
|
||||
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
)
|
||||
|
||||
func Message(msg string, args ...interface{}) ErrorOption {
|
||||
func Message(msg string, args ...any) ErrorOption {
|
||||
return func(err *gqlerror.Error) {
|
||||
err.Message += fmt.Sprintf(msg, args...)
|
||||
}
|
||||
@@ -33,7 +34,7 @@ func At(position *ast.Position) ErrorOption {
|
||||
}
|
||||
}
|
||||
|
||||
func SuggestListQuoted(prefix string, typed string, suggestions []string) ErrorOption {
|
||||
func SuggestListQuoted(prefix, typed string, suggestions []string) ErrorOption {
|
||||
suggested := SuggestionList(typed, suggestions)
|
||||
return func(err *gqlerror.Error) {
|
||||
if len(suggested) > 0 {
|
||||
@@ -42,7 +43,7 @@ func SuggestListQuoted(prefix string, typed string, suggestions []string) ErrorO
|
||||
}
|
||||
}
|
||||
|
||||
func SuggestListUnquoted(prefix string, typed string, suggestions []string) ErrorOption {
|
||||
func SuggestListUnquoted(prefix, typed string, suggestions []string) ErrorOption {
|
||||
suggested := SuggestionList(typed, suggestions)
|
||||
return func(err *gqlerror.Error) {
|
||||
if len(suggested) > 0 {
|
||||
@@ -51,7 +52,7 @@ func SuggestListUnquoted(prefix string, typed string, suggestions []string) Erro
|
||||
}
|
||||
}
|
||||
|
||||
func Suggestf(suggestion string, args ...interface{}) ErrorOption {
|
||||
func Suggestf(suggestion string, args ...any) ErrorOption {
|
||||
return func(err *gqlerror.Error) {
|
||||
err.Message += " Did you mean " + fmt.Sprintf(suggestion, args...) + "?"
|
||||
}
|
||||
@@ -117,12 +118,8 @@ func SuggestionList(input string, options []string) []string {
|
||||
func calcThreshold(a string) (threshold int) {
|
||||
// the logic is copied from here
|
||||
// https://github.com/graphql/graphql-js/blob/47bd8c8897c72d3efc17ecb1599a95cee6bac5e8/src/jsutils/suggestionList.ts#L14
|
||||
threshold = int(math.Floor(float64(len(a))*0.4) + 1)
|
||||
|
||||
if threshold < 1 {
|
||||
threshold = 1
|
||||
}
|
||||
return
|
||||
threshold = max(int(math.Floor(float64(len(a))*0.4)+1), 1)
|
||||
return threshold
|
||||
}
|
||||
|
||||
// Computes the lexical distance between strings A and B.
|
||||
@@ -136,7 +133,7 @@ func calcThreshold(a string) (threshold int) {
|
||||
// as a single edit which helps identify mis-cased values with an edit distance
|
||||
// of 1.
|
||||
//
|
||||
// This distance can be useful for detecting typos in input or sorting
|
||||
// This distance can be useful for detecting typos in input or sorting.
|
||||
func lexicalDistance(a, b string) int {
|
||||
if a == b {
|
||||
return 0
|
||||
|
||||
+9
-3
@@ -142,7 +142,11 @@ func (w *Walker) walkFragment(it *ast.FragmentDefinition) {
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Walker) walkDirectives(parentDef *ast.Definition, directives []*ast.Directive, location ast.DirectiveLocation) {
|
||||
func (w *Walker) walkDirectives(
|
||||
parentDef *ast.Definition,
|
||||
directives []*ast.Directive,
|
||||
location ast.DirectiveLocation,
|
||||
) {
|
||||
for _, dir := range directives {
|
||||
def := w.Schema.Directives[dir.Name]
|
||||
dir.Definition = def
|
||||
@@ -182,7 +186,8 @@ func (w *Walker) walkValue(value *ast.Value) {
|
||||
fieldDef := value.Definition.Fields.ForName(child.Name)
|
||||
if fieldDef != nil {
|
||||
child.Value.ExpectedType = fieldDef.Type
|
||||
child.Value.ExpectedTypeHasDefault = fieldDef.DefaultValue != nil && fieldDef.DefaultValue.Kind != ast.NullValue
|
||||
child.Value.ExpectedTypeHasDefault = fieldDef.DefaultValue != nil &&
|
||||
fieldDef.DefaultValue.Kind != ast.NullValue
|
||||
child.Value.Definition = w.Schema.Types[fieldDef.Type.Name()]
|
||||
}
|
||||
}
|
||||
@@ -209,7 +214,8 @@ func (w *Walker) walkValue(value *ast.Value) {
|
||||
func (w *Walker) walkArgument(argDef *ast.ArgumentDefinition, arg *ast.Argument) {
|
||||
if argDef != nil {
|
||||
arg.Value.ExpectedType = argDef.Type
|
||||
arg.Value.ExpectedTypeHasDefault = argDef.DefaultValue != nil && argDef.DefaultValue.Kind != ast.NullValue
|
||||
arg.Value.ExpectedTypeHasDefault = argDef.DefaultValue != nil &&
|
||||
argDef.DefaultValue.Kind != ast.NullValue
|
||||
arg.Value.Definition = w.Schema.Types[argDef.Type.Name()]
|
||||
}
|
||||
|
||||
|
||||
+19
-9
@@ -3,10 +3,8 @@ package rules
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
@@ -17,12 +15,24 @@ func ruleFuncFieldsOnCorrectType(observers *Events, addError AddErrFunc, disable
|
||||
return
|
||||
}
|
||||
|
||||
message := fmt.Sprintf(`Cannot query field "%s" on type "%s".`, field.Name, field.ObjectDefinition.Name)
|
||||
message := fmt.Sprintf(
|
||||
`Cannot query field "%s" on type "%s".`,
|
||||
field.Name,
|
||||
field.ObjectDefinition.Name,
|
||||
)
|
||||
|
||||
if !disableSuggestion {
|
||||
if suggestedTypeNames := getSuggestedTypeNames(walker, field.ObjectDefinition, field.Name); suggestedTypeNames != nil {
|
||||
message += " Did you mean to use an inline fragment on " + QuotedOrList(suggestedTypeNames...) + "?"
|
||||
} else if suggestedFieldNames := getSuggestedFieldNames(field.ObjectDefinition, field.Name); suggestedFieldNames != nil {
|
||||
if suggestedTypeNames := getSuggestedTypeNames(
|
||||
walker,
|
||||
field.ObjectDefinition,
|
||||
field.Name,
|
||||
); suggestedTypeNames != nil {
|
||||
message += " Did you mean to use an inline fragment on " + QuotedOrList(
|
||||
suggestedTypeNames...) + "?"
|
||||
} else if suggestedFieldNames := getSuggestedFieldNames(
|
||||
field.ObjectDefinition,
|
||||
field.Name,
|
||||
); suggestedFieldNames != nil {
|
||||
message += " Did you mean " + QuotedOrList(suggestedFieldNames...) + "?"
|
||||
}
|
||||
}
|
||||
@@ -89,7 +99,7 @@ func getSuggestedTypeNames(walker *Walker, parent *ast.Definition, name string)
|
||||
if diff != 0 {
|
||||
return diff < 0
|
||||
}
|
||||
return strings.Compare(typeA, typeB) < 0
|
||||
return typeA < typeB
|
||||
})
|
||||
|
||||
return suggestedTypes
|
||||
@@ -99,8 +109,8 @@ func getSuggestedTypeNames(walker *Walker, parent *ast.Definition, name string)
|
||||
// where max is set to the slice’s length,
|
||||
// we ensure that appending elements results
|
||||
// in a slice backed by a distinct array.
|
||||
// This method prevents the shared array issue
|
||||
func concatSlice(first []string, second []string) []string {
|
||||
// This method prevents the shared array issue.
|
||||
func concatSlice(first, second []string) []string {
|
||||
n := len(first)
|
||||
return append(first[:n:n], second...)
|
||||
}
|
||||
|
||||
Generated
Vendored
+11
-4
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
@@ -18,7 +17,10 @@ var FragmentsOnCompositeTypesRule = Rule{
|
||||
return
|
||||
}
|
||||
|
||||
message := fmt.Sprintf(`Fragment cannot condition on non composite type "%s".`, inlineFragment.TypeCondition)
|
||||
message := fmt.Sprintf(
|
||||
`Fragment cannot condition on non composite type "%s".`,
|
||||
inlineFragment.TypeCondition,
|
||||
)
|
||||
|
||||
addError(
|
||||
Message("%s", message),
|
||||
@@ -27,11 +29,16 @@ var FragmentsOnCompositeTypesRule = Rule{
|
||||
})
|
||||
|
||||
observers.OnFragment(func(walker *Walker, fragment *ast.FragmentDefinition) {
|
||||
if fragment.Definition == nil || fragment.TypeCondition == "" || fragment.Definition.IsCompositeType() {
|
||||
if fragment.Definition == nil || fragment.TypeCondition == "" ||
|
||||
fragment.Definition.IsCompositeType() {
|
||||
return
|
||||
}
|
||||
|
||||
message := fmt.Sprintf(`Fragment "%s" cannot condition on non composite type "%s".`, fragment.Name, fragment.TypeCondition)
|
||||
message := fmt.Sprintf(
|
||||
`Fragment "%s" cannot condition on non composite type "%s".`,
|
||||
fragment.Name,
|
||||
fragment.TypeCondition,
|
||||
)
|
||||
|
||||
addError(
|
||||
Message("%s", message),
|
||||
|
||||
+12
-3
@@ -2,7 +2,6 @@ package rules
|
||||
|
||||
import (
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
@@ -21,7 +20,12 @@ func ruleFuncKnownArgumentNames(observers *Events, addError AddErrFunc, disableS
|
||||
|
||||
if disableSuggestion {
|
||||
addError(
|
||||
Message(`Unknown argument "%s" on field "%s.%s".`, arg.Name, field.ObjectDefinition.Name, field.Name),
|
||||
Message(
|
||||
`Unknown argument "%s" on field "%s.%s".`,
|
||||
arg.Name,
|
||||
field.ObjectDefinition.Name,
|
||||
field.Name,
|
||||
),
|
||||
At(field.Position),
|
||||
)
|
||||
} else {
|
||||
@@ -30,7 +34,12 @@ func ruleFuncKnownArgumentNames(observers *Events, addError AddErrFunc, disableS
|
||||
suggestions = append(suggestions, argDef.Name)
|
||||
}
|
||||
addError(
|
||||
Message(`Unknown argument "%s" on field "%s.%s".`, arg.Name, field.ObjectDefinition.Name, field.Name),
|
||||
Message(
|
||||
`Unknown argument "%s" on field "%s.%s".`,
|
||||
arg.Name,
|
||||
field.ObjectDefinition.Name,
|
||||
field.Name,
|
||||
),
|
||||
SuggestListQuoted("Did you mean", arg.Name, suggestions),
|
||||
At(field.Position),
|
||||
)
|
||||
|
||||
+9
-6
@@ -1,8 +1,9 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
"slices"
|
||||
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
@@ -25,10 +26,8 @@ var KnownDirectivesRule = Rule{
|
||||
return
|
||||
}
|
||||
|
||||
for _, loc := range directive.Definition.Locations {
|
||||
if loc == directive.Location {
|
||||
return
|
||||
}
|
||||
if slices.Contains(directive.Definition.Locations, directive.Location) {
|
||||
return
|
||||
}
|
||||
|
||||
// position must be exists if directive.Definition != nil
|
||||
@@ -40,7 +39,11 @@ var KnownDirectivesRule = Rule{
|
||||
|
||||
if !seen[tmp] {
|
||||
addError(
|
||||
Message(`Directive "@%s" may not be used on %s.`, directive.Name, directive.Location),
|
||||
Message(
|
||||
`Directive "@%s" may not be used on %s.`,
|
||||
directive.Name,
|
||||
directive.Location,
|
||||
),
|
||||
At(directive.Position),
|
||||
)
|
||||
seen[tmp] = true
|
||||
|
||||
-1
@@ -2,7 +2,6 @@ package rules
|
||||
|
||||
import (
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
|
||||
-1
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
|
||||
-1
@@ -2,7 +2,6 @@ package rules
|
||||
|
||||
import (
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
|
||||
Generated
Vendored
-1
@@ -2,7 +2,6 @@ package rules
|
||||
|
||||
import (
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
|
||||
+10
-3
@@ -2,7 +2,6 @@ package rules
|
||||
|
||||
import (
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
@@ -29,7 +28,11 @@ var MaxIntrospectionDepth = Rule{
|
||||
},
|
||||
}
|
||||
|
||||
func checkDepthSelectionSet(selectionSet ast.SelectionSet, visitedFragments map[string]bool, depth int) bool {
|
||||
func checkDepthSelectionSet(
|
||||
selectionSet ast.SelectionSet,
|
||||
visitedFragments map[string]bool,
|
||||
depth int,
|
||||
) bool {
|
||||
for _, child := range selectionSet {
|
||||
if field, ok := child.(*ast.Field); ok {
|
||||
if checkDepthField(field, visitedFragments, depth) {
|
||||
@@ -63,7 +66,11 @@ func checkDepthField(field *ast.Field, visitedFragments map[string]bool, depth i
|
||||
return checkDepthSelectionSet(field.SelectionSet, visitedFragments, depth)
|
||||
}
|
||||
|
||||
func checkDepthFragmentSpread(fragmentSpread *ast.FragmentSpread, visitedFragments map[string]bool, depth int) bool {
|
||||
func checkDepthFragmentSpread(
|
||||
fragmentSpread *ast.FragmentSpread,
|
||||
visitedFragments map[string]bool,
|
||||
depth int,
|
||||
) bool {
|
||||
fragmentName := fragmentSpread.Name
|
||||
if visited, ok := visitedFragments[fragmentName]; ok && visited {
|
||||
// Fragment cycles are handled by `NoFragmentCyclesRule`.
|
||||
|
||||
+5
-2
@@ -5,7 +5,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
@@ -55,7 +54,11 @@ var NoFragmentCyclesRule = Rule{
|
||||
via = fmt.Sprintf(" via %s", strings.Join(fragmentNames, ", "))
|
||||
}
|
||||
addError(
|
||||
Message(`Cannot spread fragment "%s" within itself%s.`, spreadName, via),
|
||||
Message(
|
||||
`Cannot spread fragment "%s" within itself%s.`,
|
||||
spreadName,
|
||||
via,
|
||||
),
|
||||
At(spreadNode.Position),
|
||||
)
|
||||
}
|
||||
|
||||
+7
-3
@@ -2,7 +2,6 @@ package rules
|
||||
|
||||
import (
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
@@ -11,13 +10,18 @@ var NoUndefinedVariablesRule = Rule{
|
||||
Name: "NoUndefinedVariables",
|
||||
RuleFunc: func(observers *Events, addError AddErrFunc) {
|
||||
observers.OnValue(func(walker *Walker, value *ast.Value) {
|
||||
if walker.CurrentOperation == nil || value.Kind != ast.Variable || value.VariableDefinition != nil {
|
||||
if walker.CurrentOperation == nil || value.Kind != ast.Variable ||
|
||||
value.VariableDefinition != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if walker.CurrentOperation.Name != "" {
|
||||
addError(
|
||||
Message(`Variable "%s" is not defined by operation "%s".`, value, walker.CurrentOperation.Name),
|
||||
Message(
|
||||
`Variable "%s" is not defined by operation "%s".`,
|
||||
value,
|
||||
walker.CurrentOperation.Name,
|
||||
),
|
||||
At(value.Position),
|
||||
)
|
||||
} else {
|
||||
|
||||
-1
@@ -2,7 +2,6 @@ package rules
|
||||
|
||||
import (
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
|
||||
+5
-2
@@ -2,7 +2,6 @@ package rules
|
||||
|
||||
import (
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
@@ -18,7 +17,11 @@ var NoUnusedVariablesRule = Rule{
|
||||
|
||||
if operation.Name != "" {
|
||||
addError(
|
||||
Message(`Variable "$%s" is never used in operation "%s".`, varDef.Variable, operation.Name),
|
||||
Message(
|
||||
`Variable "$%s" is never used in operation "%s".`,
|
||||
varDef.Variable,
|
||||
operation.Name,
|
||||
),
|
||||
At(varDef.Position),
|
||||
)
|
||||
} else {
|
||||
|
||||
Generated
Vendored
+109
-29
@@ -6,7 +6,6 @@ import (
|
||||
"reflect"
|
||||
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
@@ -82,7 +81,8 @@ var OverlappingFieldsCanBeMergedRule = Rule{
|
||||
})
|
||||
observers.OnField(func(walker *Walker, field *ast.Field) {
|
||||
if walker.CurrentOperation == nil {
|
||||
// When checking both Operation and Fragment, errors are duplicated when processing FragmentDefinition referenced from Operation
|
||||
// When checking both Operation and Fragment, errors are duplicated when processing
|
||||
// FragmentDefinition referenced from Operation
|
||||
return
|
||||
}
|
||||
m.walker = walker
|
||||
@@ -112,7 +112,11 @@ type pairSet struct {
|
||||
data map[string]map[string]bool
|
||||
}
|
||||
|
||||
func (pairSet *pairSet) Add(a *ast.FragmentSpread, b *ast.FragmentSpread, areMutuallyExclusive bool) {
|
||||
func (pairSet *pairSet) Add(
|
||||
a *ast.FragmentSpread,
|
||||
b *ast.FragmentSpread,
|
||||
areMutuallyExclusive bool,
|
||||
) {
|
||||
add := func(a *ast.FragmentSpread, b *ast.FragmentSpread) {
|
||||
m := pairSet.data[a.Name]
|
||||
if m == nil {
|
||||
@@ -125,7 +129,11 @@ func (pairSet *pairSet) Add(a *ast.FragmentSpread, b *ast.FragmentSpread, areMut
|
||||
add(b, a)
|
||||
}
|
||||
|
||||
func (pairSet *pairSet) Has(a *ast.FragmentSpread, b *ast.FragmentSpread, areMutuallyExclusive bool) bool {
|
||||
func (pairSet *pairSet) Has(
|
||||
a *ast.FragmentSpread,
|
||||
b *ast.FragmentSpread,
|
||||
areMutuallyExclusive bool,
|
||||
) bool {
|
||||
am, ok := pairSet.data[a.Name]
|
||||
if !ok {
|
||||
return false
|
||||
@@ -224,7 +232,11 @@ func (m *ConflictMessage) addFieldsConflictMessage(addError AddErrFunc) {
|
||||
var buf bytes.Buffer
|
||||
m.String(&buf)
|
||||
addError(
|
||||
Message(`Fields "%s" conflict because %s. Use different aliases on the fields to fetch both if this was intentional.`, m.ResponseName, buf.String()),
|
||||
Message(
|
||||
`Fields "%s" conflict because %s. Use different aliases on the fields to fetch both if this was intentional.`,
|
||||
m.ResponseName,
|
||||
buf.String(),
|
||||
),
|
||||
At(m.Position),
|
||||
)
|
||||
}
|
||||
@@ -240,7 +252,9 @@ type overlappingFieldsCanBeMergedManager struct {
|
||||
comparedFragments map[string]bool
|
||||
}
|
||||
|
||||
func (m *overlappingFieldsCanBeMergedManager) findConflictsWithinSelectionSet(selectionSet ast.SelectionSet) []*ConflictMessage {
|
||||
func (m *overlappingFieldsCanBeMergedManager) findConflictsWithinSelectionSet(
|
||||
selectionSet ast.SelectionSet,
|
||||
) []*ConflictMessage {
|
||||
if len(selectionSet) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -271,7 +285,12 @@ func (m *overlappingFieldsCanBeMergedManager) findConflictsWithinSelectionSet(se
|
||||
return conflicts.Conflicts
|
||||
}
|
||||
|
||||
func (m *overlappingFieldsCanBeMergedManager) collectConflictsBetweenFieldsAndFragment(conflicts *conflictMessageContainer, areMutuallyExclusive bool, fieldsMap *sequentialFieldsMap, fragmentSpread *ast.FragmentSpread) {
|
||||
func (m *overlappingFieldsCanBeMergedManager) collectConflictsBetweenFieldsAndFragment(
|
||||
conflicts *conflictMessageContainer,
|
||||
areMutuallyExclusive bool,
|
||||
fieldsMap *sequentialFieldsMap,
|
||||
fragmentSpread *ast.FragmentSpread,
|
||||
) {
|
||||
if m.comparedFragments[fragmentSpread.Name] {
|
||||
return
|
||||
}
|
||||
@@ -299,11 +318,21 @@ func (m *overlappingFieldsCanBeMergedManager) collectConflictsBetweenFieldsAndFr
|
||||
if fragmentSpread.Name == baseFragmentSpread.Name {
|
||||
continue
|
||||
}
|
||||
m.collectConflictsBetweenFieldsAndFragment(conflicts, areMutuallyExclusive, fieldsMap, fragmentSpread)
|
||||
m.collectConflictsBetweenFieldsAndFragment(
|
||||
conflicts,
|
||||
areMutuallyExclusive,
|
||||
fieldsMap,
|
||||
fragmentSpread,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *overlappingFieldsCanBeMergedManager) collectConflictsBetweenFragments(conflicts *conflictMessageContainer, areMutuallyExclusive bool, fragmentSpreadA *ast.FragmentSpread, fragmentSpreadB *ast.FragmentSpread) {
|
||||
func (m *overlappingFieldsCanBeMergedManager) collectConflictsBetweenFragments(
|
||||
conflicts *conflictMessageContainer,
|
||||
areMutuallyExclusive bool,
|
||||
fragmentSpreadA *ast.FragmentSpread,
|
||||
fragmentSpreadB *ast.FragmentSpread,
|
||||
) {
|
||||
var check func(fragmentSpreadA *ast.FragmentSpread, fragmentSpreadB *ast.FragmentSpread)
|
||||
check = func(fragmentSpreadA *ast.FragmentSpread, fragmentSpreadB *ast.FragmentSpread) {
|
||||
if fragmentSpreadA.Name == fragmentSpreadB.Name {
|
||||
@@ -322,8 +351,12 @@ func (m *overlappingFieldsCanBeMergedManager) collectConflictsBetweenFragments(c
|
||||
return
|
||||
}
|
||||
|
||||
fieldsMapA, fragmentSpreadsA := getFieldsAndFragmentNames(fragmentSpreadA.Definition.SelectionSet)
|
||||
fieldsMapB, fragmentSpreadsB := getFieldsAndFragmentNames(fragmentSpreadB.Definition.SelectionSet)
|
||||
fieldsMapA, fragmentSpreadsA := getFieldsAndFragmentNames(
|
||||
fragmentSpreadA.Definition.SelectionSet,
|
||||
)
|
||||
fieldsMapB, fragmentSpreadsB := getFieldsAndFragmentNames(
|
||||
fragmentSpreadB.Definition.SelectionSet,
|
||||
)
|
||||
|
||||
// (F) First, collect all conflicts between these two collections of fields
|
||||
// (not including any nested fragments).
|
||||
@@ -344,7 +377,11 @@ func (m *overlappingFieldsCanBeMergedManager) collectConflictsBetweenFragments(c
|
||||
check(fragmentSpreadA, fragmentSpreadB)
|
||||
}
|
||||
|
||||
func (m *overlappingFieldsCanBeMergedManager) findConflictsBetweenSubSelectionSets(areMutuallyExclusive bool, selectionSetA ast.SelectionSet, selectionSetB ast.SelectionSet) *conflictMessageContainer {
|
||||
func (m *overlappingFieldsCanBeMergedManager) findConflictsBetweenSubSelectionSets(
|
||||
areMutuallyExclusive bool,
|
||||
selectionSetA ast.SelectionSet,
|
||||
selectionSetB ast.SelectionSet,
|
||||
) *conflictMessageContainer {
|
||||
var conflicts conflictMessageContainer
|
||||
|
||||
fieldsMapA, fragmentSpreadsA := getFieldsAndFragmentNames(selectionSetA)
|
||||
@@ -357,14 +394,24 @@ func (m *overlappingFieldsCanBeMergedManager) findConflictsBetweenSubSelectionSe
|
||||
// those referenced by each fragment name associated with the second.
|
||||
for _, fragmentSpread := range fragmentSpreadsB {
|
||||
m.comparedFragments = make(map[string]bool)
|
||||
m.collectConflictsBetweenFieldsAndFragment(&conflicts, areMutuallyExclusive, fieldsMapA, fragmentSpread)
|
||||
m.collectConflictsBetweenFieldsAndFragment(
|
||||
&conflicts,
|
||||
areMutuallyExclusive,
|
||||
fieldsMapA,
|
||||
fragmentSpread,
|
||||
)
|
||||
}
|
||||
|
||||
// (I) Then collect conflicts between the second collection of fields and
|
||||
// those referenced by each fragment name associated with the first.
|
||||
for _, fragmentSpread := range fragmentSpreadsA {
|
||||
m.comparedFragments = make(map[string]bool)
|
||||
m.collectConflictsBetweenFieldsAndFragment(&conflicts, areMutuallyExclusive, fieldsMapB, fragmentSpread)
|
||||
m.collectConflictsBetweenFieldsAndFragment(
|
||||
&conflicts,
|
||||
areMutuallyExclusive,
|
||||
fieldsMapB,
|
||||
fragmentSpread,
|
||||
)
|
||||
}
|
||||
|
||||
// (J) Also collect conflicts between any fragment names by the first and
|
||||
@@ -372,7 +419,12 @@ func (m *overlappingFieldsCanBeMergedManager) findConflictsBetweenSubSelectionSe
|
||||
// names to each item in the second set of names.
|
||||
for _, fragmentSpreadA := range fragmentSpreadsA {
|
||||
for _, fragmentSpreadB := range fragmentSpreadsB {
|
||||
m.collectConflictsBetweenFragments(&conflicts, areMutuallyExclusive, fragmentSpreadA, fragmentSpreadB)
|
||||
m.collectConflictsBetweenFragments(
|
||||
&conflicts,
|
||||
areMutuallyExclusive,
|
||||
fragmentSpreadA,
|
||||
fragmentSpreadB,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,7 +435,10 @@ func (m *overlappingFieldsCanBeMergedManager) findConflictsBetweenSubSelectionSe
|
||||
return &conflicts
|
||||
}
|
||||
|
||||
func (m *overlappingFieldsCanBeMergedManager) collectConflictsWithin(conflicts *conflictMessageContainer, fieldsMap *sequentialFieldsMap) {
|
||||
func (m *overlappingFieldsCanBeMergedManager) collectConflictsWithin(
|
||||
conflicts *conflictMessageContainer,
|
||||
fieldsMap *sequentialFieldsMap,
|
||||
) {
|
||||
for _, fields := range fieldsMap.Iterator() {
|
||||
for idx, fieldA := range fields {
|
||||
for _, fieldB := range fields[idx+1:] {
|
||||
@@ -396,7 +451,12 @@ func (m *overlappingFieldsCanBeMergedManager) collectConflictsWithin(conflicts *
|
||||
}
|
||||
}
|
||||
|
||||
func (m *overlappingFieldsCanBeMergedManager) collectConflictsBetween(conflicts *conflictMessageContainer, parentFieldsAreMutuallyExclusive bool, fieldsMapA *sequentialFieldsMap, fieldsMapB *sequentialFieldsMap) {
|
||||
func (m *overlappingFieldsCanBeMergedManager) collectConflictsBetween(
|
||||
conflicts *conflictMessageContainer,
|
||||
parentFieldsAreMutuallyExclusive bool,
|
||||
fieldsMapA *sequentialFieldsMap,
|
||||
fieldsMapB *sequentialFieldsMap,
|
||||
) {
|
||||
for _, fieldsEntryA := range fieldsMapA.KeyValueIterator() {
|
||||
fieldsB, ok := fieldsMapB.Get(fieldsEntryA.ResponseName)
|
||||
if !ok {
|
||||
@@ -413,7 +473,11 @@ func (m *overlappingFieldsCanBeMergedManager) collectConflictsBetween(conflicts
|
||||
}
|
||||
}
|
||||
|
||||
func (m *overlappingFieldsCanBeMergedManager) findConflict(parentFieldsAreMutuallyExclusive bool, fieldA *ast.Field, fieldB *ast.Field) *ConflictMessage {
|
||||
func (m *overlappingFieldsCanBeMergedManager) findConflict(
|
||||
parentFieldsAreMutuallyExclusive bool,
|
||||
fieldA *ast.Field,
|
||||
fieldB *ast.Field,
|
||||
) *ConflictMessage {
|
||||
if fieldA.ObjectDefinition == nil || fieldB.ObjectDefinition == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -437,8 +501,12 @@ func (m *overlappingFieldsCanBeMergedManager) findConflict(parentFieldsAreMutual
|
||||
if fieldA.Name != fieldB.Name {
|
||||
return &ConflictMessage{
|
||||
ResponseName: fieldNameA,
|
||||
Message: fmt.Sprintf(`"%s" and "%s" are different fields`, fieldA.Name, fieldB.Name),
|
||||
Position: fieldB.Position,
|
||||
Message: fmt.Sprintf(
|
||||
`"%s" and "%s" are different fields`,
|
||||
fieldA.Name,
|
||||
fieldB.Name,
|
||||
),
|
||||
Position: fieldB.Position,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,18 +520,27 @@ func (m *overlappingFieldsCanBeMergedManager) findConflict(parentFieldsAreMutual
|
||||
}
|
||||
}
|
||||
|
||||
if fieldA.Definition != nil && fieldB.Definition != nil && doTypesConflict(m.walker, fieldA.Definition.Type, fieldB.Definition.Type) {
|
||||
if fieldA.Definition != nil && fieldB.Definition != nil &&
|
||||
doTypesConflict(m.walker, fieldA.Definition.Type, fieldB.Definition.Type) {
|
||||
return &ConflictMessage{
|
||||
ResponseName: fieldNameA,
|
||||
Message: fmt.Sprintf(`they return conflicting types "%s" and "%s"`, fieldA.Definition.Type.String(), fieldB.Definition.Type.String()),
|
||||
Position: fieldB.Position,
|
||||
Message: fmt.Sprintf(
|
||||
`they return conflicting types "%s" and "%s"`,
|
||||
fieldA.Definition.Type.String(),
|
||||
fieldB.Definition.Type.String(),
|
||||
),
|
||||
Position: fieldB.Position,
|
||||
}
|
||||
}
|
||||
|
||||
// Collect and compare sub-fields. Use the same "visited fragment names" list
|
||||
// for both collections so fields in a fragment reference are never
|
||||
// compared to themselves.
|
||||
conflicts := m.findConflictsBetweenSubSelectionSets(areMutuallyExclusive, fieldA.SelectionSet, fieldB.SelectionSet)
|
||||
conflicts := m.findConflictsBetweenSubSelectionSets(
|
||||
areMutuallyExclusive,
|
||||
fieldA.SelectionSet,
|
||||
fieldB.SelectionSet,
|
||||
)
|
||||
if conflicts == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -474,7 +551,7 @@ func (m *overlappingFieldsCanBeMergedManager) findConflict(parentFieldsAreMutual
|
||||
}
|
||||
}
|
||||
|
||||
func sameArguments(args1 []*ast.Argument, args2 []*ast.Argument) bool {
|
||||
func sameArguments(args1, args2 []*ast.Argument) bool {
|
||||
if len(args1) != len(args2) {
|
||||
return false
|
||||
}
|
||||
@@ -493,7 +570,7 @@ func sameArguments(args1 []*ast.Argument, args2 []*ast.Argument) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func sameValue(value1 *ast.Value, value2 *ast.Value) bool {
|
||||
func sameValue(value1, value2 *ast.Value) bool {
|
||||
if value1.Kind != value2.Kind {
|
||||
return false
|
||||
}
|
||||
@@ -503,7 +580,7 @@ func sameValue(value1 *ast.Value, value2 *ast.Value) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func doTypesConflict(walker *Walker, type1 *ast.Type, type2 *ast.Type) bool {
|
||||
func doTypesConflict(walker *Walker, type1, type2 *ast.Type) bool {
|
||||
if type1.Elem != nil {
|
||||
if type2.Elem != nil {
|
||||
return doTypesConflict(walker, type1.Elem, type2.Elem)
|
||||
@@ -522,14 +599,17 @@ func doTypesConflict(walker *Walker, type1 *ast.Type, type2 *ast.Type) bool {
|
||||
|
||||
t1 := walker.Schema.Types[type1.NamedType]
|
||||
t2 := walker.Schema.Types[type2.NamedType]
|
||||
if (t1.Kind == ast.Scalar || t1.Kind == ast.Enum) && (t2.Kind == ast.Scalar || t2.Kind == ast.Enum) {
|
||||
if (t1.Kind == ast.Scalar || t1.Kind == ast.Enum) &&
|
||||
(t2.Kind == ast.Scalar || t2.Kind == ast.Enum) {
|
||||
return t1.Name != t2.Name
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func getFieldsAndFragmentNames(selectionSet ast.SelectionSet) (*sequentialFieldsMap, []*ast.FragmentSpread) {
|
||||
func getFieldsAndFragmentNames(
|
||||
selectionSet ast.SelectionSet,
|
||||
) (*sequentialFieldsMap, []*ast.FragmentSpread) {
|
||||
fieldsMap := sequentialFieldsMap{
|
||||
data: make(map[string][]*ast.Field),
|
||||
}
|
||||
|
||||
Generated
Vendored
+21
-8
@@ -2,7 +2,6 @@ package rules
|
||||
|
||||
import (
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
@@ -49,7 +48,11 @@ var PossibleFragmentSpreadsRule = Rule{
|
||||
observers.OnInlineFragment(func(walker *Walker, inlineFragment *ast.InlineFragment) {
|
||||
validate(walker, inlineFragment.ObjectDefinition, inlineFragment.TypeCondition, func() {
|
||||
addError(
|
||||
Message(`Fragment cannot be spread here as objects of type "%s" can never be of type "%s".`, inlineFragment.ObjectDefinition.Name, inlineFragment.TypeCondition),
|
||||
Message(
|
||||
`Fragment cannot be spread here as objects of type "%s" can never be of type "%s".`,
|
||||
inlineFragment.ObjectDefinition.Name,
|
||||
inlineFragment.TypeCondition,
|
||||
),
|
||||
At(inlineFragment.Position),
|
||||
)
|
||||
})
|
||||
@@ -59,12 +62,22 @@ var PossibleFragmentSpreadsRule = Rule{
|
||||
if fragmentSpread.Definition == nil {
|
||||
return
|
||||
}
|
||||
validate(walker, fragmentSpread.ObjectDefinition, fragmentSpread.Definition.TypeCondition, func() {
|
||||
addError(
|
||||
Message(`Fragment "%s" cannot be spread here as objects of type "%s" can never be of type "%s".`, fragmentSpread.Name, fragmentSpread.ObjectDefinition.Name, fragmentSpread.Definition.TypeCondition),
|
||||
At(fragmentSpread.Position),
|
||||
)
|
||||
})
|
||||
validate(
|
||||
walker,
|
||||
fragmentSpread.ObjectDefinition,
|
||||
fragmentSpread.Definition.TypeCondition,
|
||||
func() {
|
||||
addError(
|
||||
Message(
|
||||
`Fragment "%s" cannot be spread here as objects of type "%s" can never be of type "%s".`,
|
||||
fragmentSpread.Name,
|
||||
fragmentSpread.ObjectDefinition.Name,
|
||||
fragmentSpread.Definition.TypeCondition,
|
||||
),
|
||||
At(fragmentSpread.Position),
|
||||
)
|
||||
},
|
||||
)
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
+10
-3
@@ -2,7 +2,6 @@ package rules
|
||||
|
||||
import (
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
@@ -22,14 +21,22 @@ var ScalarLeafsRule = Rule{
|
||||
|
||||
if fieldType.IsLeafType() && len(field.SelectionSet) > 0 {
|
||||
addError(
|
||||
Message(`Field "%s" must not have a selection since type "%s" has no subfields.`, field.Name, fieldType.Name),
|
||||
Message(
|
||||
`Field "%s" must not have a selection since type "%s" has no subfields.`,
|
||||
field.Name,
|
||||
fieldType.Name,
|
||||
),
|
||||
At(field.Position),
|
||||
)
|
||||
}
|
||||
|
||||
if !fieldType.IsLeafType() && len(field.SelectionSet) == 0 {
|
||||
addError(
|
||||
Message(`Field "%s" of type "%s" must have a selection of subfields.`, field.Name, field.Definition.Type.String()),
|
||||
Message(
|
||||
`Field "%s" of type "%s" must have a selection of subfields.`,
|
||||
field.Name,
|
||||
field.Definition.Type.String(),
|
||||
),
|
||||
Suggestf(`"%s { ... }"`, field.Name),
|
||||
At(field.Position),
|
||||
)
|
||||
|
||||
Generated
Vendored
-1
@@ -5,7 +5,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
|
||||
-1
@@ -2,7 +2,6 @@ package rules
|
||||
|
||||
import (
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
|
||||
Generated
Vendored
+4
-2
@@ -2,7 +2,6 @@ package rules
|
||||
|
||||
import (
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
@@ -16,7 +15,10 @@ var UniqueDirectivesPerLocationRule = Rule{
|
||||
for _, dir := range directives {
|
||||
if dir.Name != "repeatable" && seen[dir.Name] {
|
||||
addError(
|
||||
Message(`The directive "@%s" can only be used once at this location.`, dir.Name),
|
||||
Message(
|
||||
`The directive "@%s" can only be used once at this location.`,
|
||||
dir.Name,
|
||||
),
|
||||
At(dir.Position),
|
||||
)
|
||||
}
|
||||
|
||||
-1
@@ -2,7 +2,6 @@ package rules
|
||||
|
||||
import (
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
|
||||
Generated
Vendored
-1
@@ -2,7 +2,6 @@ package rules
|
||||
|
||||
import (
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
|
||||
-1
@@ -2,7 +2,6 @@ package rules
|
||||
|
||||
import (
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
|
||||
-1
@@ -2,7 +2,6 @@ package rules
|
||||
|
||||
import (
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
|
||||
+78
-17
@@ -6,7 +6,6 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
@@ -19,7 +18,11 @@ func ruleFuncValuesOfCorrectType(observers *Events, addError AddErrFunc, disable
|
||||
|
||||
if value.Kind == ast.NullValue && value.ExpectedType.NonNull {
|
||||
addError(
|
||||
Message(`Expected value of type "%s", found %s.`, value.ExpectedType.String(), value.String()),
|
||||
Message(
|
||||
`Expected value of type "%s", found %s.`,
|
||||
value.ExpectedType.String(),
|
||||
value.String(),
|
||||
),
|
||||
At(value.Position),
|
||||
)
|
||||
}
|
||||
@@ -66,13 +69,21 @@ func ruleFuncValuesOfCorrectType(observers *Events, addError AddErrFunc, disable
|
||||
if value.Definition.Kind == ast.Enum {
|
||||
if disableSuggestion {
|
||||
addError(
|
||||
Message(`Enum "%s" cannot represent non-enum value: %s.`, value.ExpectedType.String(), value.String()),
|
||||
Message(
|
||||
`Enum "%s" cannot represent non-enum value: %s.`,
|
||||
value.ExpectedType.String(),
|
||||
value.String(),
|
||||
),
|
||||
At(value.Position),
|
||||
)
|
||||
} else {
|
||||
rawValStr := fmt.Sprint(rawVal)
|
||||
addError(
|
||||
Message(`Enum "%s" cannot represent non-enum value: %s.`, value.ExpectedType.String(), value.String()),
|
||||
Message(
|
||||
`Enum "%s" cannot represent non-enum value: %s.`,
|
||||
value.ExpectedType.String(),
|
||||
value.String(),
|
||||
),
|
||||
SuggestListQuoted("Did you mean the enum value", rawValStr, possibleEnums),
|
||||
At(value.Position),
|
||||
)
|
||||
@@ -92,20 +103,32 @@ func ruleFuncValuesOfCorrectType(observers *Events, addError AddErrFunc, disable
|
||||
rawValStr := fmt.Sprint(rawVal)
|
||||
addError(
|
||||
unexpectedTypeMessageOnly(value),
|
||||
SuggestListUnquoted("Did you mean the enum value", rawValStr, possibleEnums),
|
||||
SuggestListUnquoted(
|
||||
"Did you mean the enum value",
|
||||
rawValStr,
|
||||
possibleEnums,
|
||||
),
|
||||
At(value.Position),
|
||||
)
|
||||
}
|
||||
} else if value.Definition.EnumValues.ForName(value.Raw) == nil {
|
||||
if disableSuggestion {
|
||||
addError(
|
||||
Message(`Value "%s" does not exist in "%s" enum.`, value.String(), value.ExpectedType.String()),
|
||||
Message(
|
||||
`Value "%s" does not exist in "%s" enum.`,
|
||||
value.String(),
|
||||
value.ExpectedType.String(),
|
||||
),
|
||||
At(value.Position),
|
||||
)
|
||||
} else {
|
||||
rawValStr := fmt.Sprint(rawVal)
|
||||
addError(
|
||||
Message(`Value "%s" does not exist in "%s" enum.`, value.String(), value.ExpectedType.String()),
|
||||
Message(
|
||||
`Value "%s" does not exist in "%s" enum.`,
|
||||
value.String(),
|
||||
value.ExpectedType.String(),
|
||||
),
|
||||
SuggestListQuoted("Did you mean the enum value", rawValStr, possibleEnums),
|
||||
At(value.Position),
|
||||
)
|
||||
@@ -124,7 +147,12 @@ func ruleFuncValuesOfCorrectType(observers *Events, addError AddErrFunc, disable
|
||||
fieldValue := value.Children.ForName(field.Name)
|
||||
if fieldValue == nil && field.DefaultValue == nil {
|
||||
addError(
|
||||
Message(`Field "%s.%s" of required type "%s" was not provided.`, value.Definition.Name, field.Name, field.Type.String()),
|
||||
Message(
|
||||
`Field "%s.%s" of required type "%s" was not provided.`,
|
||||
value.Definition.Name,
|
||||
field.Name,
|
||||
field.Type.String(),
|
||||
),
|
||||
At(value.Position),
|
||||
)
|
||||
continue
|
||||
@@ -137,7 +165,10 @@ func ruleFuncValuesOfCorrectType(observers *Events, addError AddErrFunc, disable
|
||||
func() {
|
||||
if len(value.Children) != 1 {
|
||||
addError(
|
||||
Message(`OneOf Input Object "%s" must specify exactly one key.`, value.Definition.Name),
|
||||
Message(
|
||||
`OneOf Input Object "%s" must specify exactly one key.`,
|
||||
value.Definition.Name,
|
||||
),
|
||||
At(value.Position),
|
||||
)
|
||||
return
|
||||
@@ -147,7 +178,11 @@ func ruleFuncValuesOfCorrectType(observers *Events, addError AddErrFunc, disable
|
||||
isNullLiteral := fieldValue == nil || fieldValue.Kind == ast.NullValue
|
||||
if isNullLiteral {
|
||||
addError(
|
||||
Message(`Field "%s.%s" must be non-null.`, value.Definition.Name, value.Definition.Fields[0].Name),
|
||||
Message(
|
||||
`Field "%s.%s" must be non-null.`,
|
||||
value.Definition.Name,
|
||||
value.Definition.Fields[0].Name,
|
||||
),
|
||||
At(fieldValue.Position),
|
||||
)
|
||||
return
|
||||
@@ -159,7 +194,11 @@ func ruleFuncValuesOfCorrectType(observers *Events, addError AddErrFunc, disable
|
||||
isNullableVariable := !fieldValue.VariableDefinition.Type.NonNull
|
||||
if isNullableVariable {
|
||||
addError(
|
||||
Message(`Variable "%s" must be non-nullable to be used for OneOf Input Object "%s".`, variableName, value.Definition.Name),
|
||||
Message(
|
||||
`Variable "%s" must be non-nullable to be used for OneOf Input Object "%s".`,
|
||||
variableName,
|
||||
value.Definition.Name,
|
||||
),
|
||||
At(fieldValue.Position),
|
||||
)
|
||||
}
|
||||
@@ -172,7 +211,11 @@ func ruleFuncValuesOfCorrectType(observers *Events, addError AddErrFunc, disable
|
||||
if value.Definition.Fields.ForName(fieldValue.Name) == nil {
|
||||
if disableSuggestion {
|
||||
addError(
|
||||
Message(`Field "%s" is not defined by type "%s".`, fieldValue.Name, value.Definition.Name),
|
||||
Message(
|
||||
`Field "%s" is not defined by type "%s".`,
|
||||
fieldValue.Name,
|
||||
value.Definition.Name,
|
||||
),
|
||||
At(fieldValue.Position),
|
||||
)
|
||||
} else {
|
||||
@@ -182,7 +225,11 @@ func ruleFuncValuesOfCorrectType(observers *Events, addError AddErrFunc, disable
|
||||
}
|
||||
|
||||
addError(
|
||||
Message(`Field "%s" is not defined by type "%s".`, fieldValue.Name, value.Definition.Name),
|
||||
Message(
|
||||
`Field "%s" is not defined by type "%s".`,
|
||||
fieldValue.Name,
|
||||
value.Definition.Name,
|
||||
),
|
||||
SuggestListQuoted("Did you mean", fieldValue.Name, suggestions),
|
||||
At(fieldValue.Position),
|
||||
)
|
||||
@@ -223,7 +270,12 @@ func unexpectedTypeMessage(addError AddErrFunc, v *ast.Value) {
|
||||
func unexpectedTypeMessageOnly(v *ast.Value) ErrorOption {
|
||||
switch v.ExpectedType.String() {
|
||||
case "Int", "Int!":
|
||||
if _, err := strconv.ParseInt(v.Raw, 10, 32); err != nil && errors.Is(err, strconv.ErrRange) {
|
||||
if _, err := strconv.ParseInt(
|
||||
v.Raw,
|
||||
10,
|
||||
32,
|
||||
); err != nil &&
|
||||
errors.Is(err, strconv.ErrRange) {
|
||||
return Message(`Int cannot represent non 32-bit signed integer value: %s`, v.String())
|
||||
}
|
||||
return Message(`Int cannot represent non-integer value: %s`, v.String())
|
||||
@@ -236,11 +288,20 @@ func unexpectedTypeMessageOnly(v *ast.Value) ErrorOption {
|
||||
case "ID", "ID!":
|
||||
return Message(`ID cannot represent a non-string and non-integer value: %s`, v.String())
|
||||
// case "Enum":
|
||||
// return Message(`Enum "%s" cannot represent non-enum value: %s`, v.ExpectedType.String(), v.String())
|
||||
// return Message(`Enum "%s" cannot represent non-enum value: %s`, v.ExpectedType.String(),
|
||||
// v.String())
|
||||
default:
|
||||
if v.Definition.Kind == ast.Enum {
|
||||
return Message(`Enum "%s" cannot represent non-enum value: %s.`, v.ExpectedType.String(), v.String())
|
||||
return Message(
|
||||
`Enum "%s" cannot represent non-enum value: %s.`,
|
||||
v.ExpectedType.String(),
|
||||
v.String(),
|
||||
)
|
||||
}
|
||||
return Message(`Expected value of type "%s", found %s.`, v.ExpectedType.String(), v.String())
|
||||
return Message(
|
||||
`Expected value of type "%s", found %s.`,
|
||||
v.ExpectedType.String(),
|
||||
v.String(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Generated
Vendored
-1
@@ -2,7 +2,6 @@ package rules
|
||||
|
||||
import (
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
//nolint:staticcheck // Validator rules each use dot imports for convenience.
|
||||
. "github.com/vektah/gqlparser/v2/validator/core"
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user