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:
dependabot[bot]
2026-03-09 14:26:08 +01:00
committed by Ralf Haferkamp
parent accbd90b43
commit c288b91312
123 changed files with 12459 additions and 11151 deletions
+4 -5
View File
@@ -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
+12 -15
View File
@@ -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
View File
@@ -1,6 +1,3 @@
//go:build go1.16
// +build go1.16
package bundle
import (
-3
View File
@@ -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 (
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -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
View File
@@ -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
View File
@@ -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
}
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
@@ -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
View File
@@ -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
}
/**
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1,6 +1,3 @@
//go:build go1.16
// +build go1.16
package bundle
import (
-4
View File
@@ -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 {
@@ -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
View File
@@ -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
View File
@@ -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
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-287
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1,5 +1,4 @@
//go:build !go1.18 || !darwin
// +build !go1.18 !darwin
//go:build !darwin
package topdown
@@ -1,6 +1,3 @@
//go:build go1.18
// +build go1.18
package topdown
func fixupDarwinGo118(x, y string) string {
+95 -118
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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()]
}
@@ -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 slices 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...)
}
@@ -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),
@@ -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
View File
@@ -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
@@ -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"
)
@@ -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"
)
@@ -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"
)
@@ -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"
)
@@ -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,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),
)
}
@@ -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 {
@@ -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"
)
@@ -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 {
@@ -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),
}
@@ -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
View File
@@ -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),
)
@@ -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"
)
@@ -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"
)
@@ -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),
)
}
@@ -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"
)
@@ -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"
)
@@ -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"
)
@@ -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"
)
@@ -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(),
)
}
}
@@ -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