vendor: github.com/open-policy-agent/opa v1.14.1
updating to the lowest minor release that contains [opa@e9ca3ed], which removed some redundant imports that resulted in indirect dependencies. full diff: https://github.com/open-policy-agent/opa/compare/v1.10.1...v1.14.1 [opa@e9ca3ed]: https://github.com/open-policy-agent/opa/commit/e9ca3ed4151e5f1850b379aa62cad77669f453a5 Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
@@ -38,7 +38,7 @@ require (
|
||||
github.com/moby/sys/atomicwriter v0.1.0
|
||||
github.com/moby/sys/mountinfo v0.7.2
|
||||
github.com/morikuni/aec v1.1.0
|
||||
github.com/open-policy-agent/opa v1.10.1
|
||||
github.com/open-policy-agent/opa v1.14.1
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.1.1
|
||||
github.com/pelletier/go-toml/v2 v2.3.1
|
||||
@@ -97,7 +97,6 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.43.3 // indirect
|
||||
github.com/aws/smithy-go v1.27.2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
@@ -118,7 +117,6 @@ require (
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fvbommel/sortorder v1.1.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/analysis v0.25.2 // indirect
|
||||
@@ -184,10 +182,6 @@ require (
|
||||
github.com/oklog/ulid/v2 v2.1.1 // indirect
|
||||
github.com/package-url/packageurl-go v0.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.5 // indirect
|
||||
github.com/prometheus/procfs v0.20.1 // indirect
|
||||
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect
|
||||
|
||||
@@ -98,8 +98,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
|
||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/bytecodealliance/wasmtime-go/v37 v37.0.0 h1:DPjdn2V3JhXHMoZ2ymRqGK+y1bDyr9wgpyYCvhjMky8=
|
||||
github.com/bytecodealliance/wasmtime-go/v37 v37.0.0/go.mod h1:Pf1l2JCTUFMnOqDIwkjzx1qfVJ09xbaXETKgRVE4jZ0=
|
||||
github.com/bytecodealliance/wasmtime-go/v39 v39.0.1 h1:RibaT47yiyCRxMOj/l2cvL8cWiWBSqDXHyqsa9sGcCE=
|
||||
github.com/bytecodealliance/wasmtime-go/v39 v39.0.1/go.mod h1:miR4NYIEBXeDNamZIzpskhJ0z/p8al+lwMWylQ/ZJb4=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
@@ -203,16 +203,14 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI=
|
||||
github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=
|
||||
github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0=
|
||||
github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=
|
||||
github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw=
|
||||
github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM=
|
||||
github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
|
||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
@@ -449,8 +447,8 @@ github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0
|
||||
github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM=
|
||||
github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=
|
||||
github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
|
||||
github.com/open-policy-agent/opa v1.10.1 h1:haIvxZSPky8HLjRrvQwWAjCPLg8JDFSZMbbG4yyUHgY=
|
||||
github.com/open-policy-agent/opa v1.10.1/go.mod h1:7uPI3iRpOalJ0BhK6s1JALWPU9HvaV1XeBSSMZnr/PM=
|
||||
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/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
Copyright (C) 2013 Blake Mizerany
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
-2388
File diff suppressed because it is too large
Load Diff
-316
@@ -1,316 +0,0 @@
|
||||
// Package quantile computes approximate quantiles over an unbounded data
|
||||
// stream within low memory and CPU bounds.
|
||||
//
|
||||
// A small amount of accuracy is traded to achieve the above properties.
|
||||
//
|
||||
// Multiple streams can be merged before calling Query to generate a single set
|
||||
// of results. This is meaningful when the streams represent the same type of
|
||||
// data. See Merge and Samples.
|
||||
//
|
||||
// For more detailed information about the algorithm used, see:
|
||||
//
|
||||
// Effective Computation of Biased Quantiles over Data Streams
|
||||
//
|
||||
// http://www.cs.rutgers.edu/~muthu/bquant.pdf
|
||||
package quantile
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Sample holds an observed value and meta information for compression. JSON
|
||||
// tags have been added for convenience.
|
||||
type Sample struct {
|
||||
Value float64 `json:",string"`
|
||||
Width float64 `json:",string"`
|
||||
Delta float64 `json:",string"`
|
||||
}
|
||||
|
||||
// Samples represents a slice of samples. It implements sort.Interface.
|
||||
type Samples []Sample
|
||||
|
||||
func (a Samples) Len() int { return len(a) }
|
||||
func (a Samples) Less(i, j int) bool { return a[i].Value < a[j].Value }
|
||||
func (a Samples) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
type invariant func(s *stream, r float64) float64
|
||||
|
||||
// NewLowBiased returns an initialized Stream for low-biased quantiles
|
||||
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
|
||||
// error guarantees can still be given even for the lower ranks of the data
|
||||
// distribution.
|
||||
//
|
||||
// The provided epsilon is a relative error, i.e. the true quantile of a value
|
||||
// returned by a query is guaranteed to be within (1±Epsilon)*Quantile.
|
||||
//
|
||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
|
||||
// properties.
|
||||
func NewLowBiased(epsilon float64) *Stream {
|
||||
ƒ := func(s *stream, r float64) float64 {
|
||||
return 2 * epsilon * r
|
||||
}
|
||||
return newStream(ƒ)
|
||||
}
|
||||
|
||||
// NewHighBiased returns an initialized Stream for high-biased quantiles
|
||||
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
|
||||
// error guarantees can still be given even for the higher ranks of the data
|
||||
// distribution.
|
||||
//
|
||||
// The provided epsilon is a relative error, i.e. the true quantile of a value
|
||||
// returned by a query is guaranteed to be within 1-(1±Epsilon)*(1-Quantile).
|
||||
//
|
||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
|
||||
// properties.
|
||||
func NewHighBiased(epsilon float64) *Stream {
|
||||
ƒ := func(s *stream, r float64) float64 {
|
||||
return 2 * epsilon * (s.n - r)
|
||||
}
|
||||
return newStream(ƒ)
|
||||
}
|
||||
|
||||
// NewTargeted returns an initialized Stream concerned with a particular set of
|
||||
// quantile values that are supplied a priori. Knowing these a priori reduces
|
||||
// space and computation time. The targets map maps the desired quantiles to
|
||||
// their absolute errors, i.e. the true quantile of a value returned by a query
|
||||
// is guaranteed to be within (Quantile±Epsilon).
|
||||
//
|
||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties.
|
||||
func NewTargeted(targetMap map[float64]float64) *Stream {
|
||||
// Convert map to slice to avoid slow iterations on a map.
|
||||
// ƒ is called on the hot path, so converting the map to a slice
|
||||
// beforehand results in significant CPU savings.
|
||||
targets := targetMapToSlice(targetMap)
|
||||
|
||||
ƒ := func(s *stream, r float64) float64 {
|
||||
var m = math.MaxFloat64
|
||||
var f float64
|
||||
for _, t := range targets {
|
||||
if t.quantile*s.n <= r {
|
||||
f = (2 * t.epsilon * r) / t.quantile
|
||||
} else {
|
||||
f = (2 * t.epsilon * (s.n - r)) / (1 - t.quantile)
|
||||
}
|
||||
if f < m {
|
||||
m = f
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
return newStream(ƒ)
|
||||
}
|
||||
|
||||
type target struct {
|
||||
quantile float64
|
||||
epsilon float64
|
||||
}
|
||||
|
||||
func targetMapToSlice(targetMap map[float64]float64) []target {
|
||||
targets := make([]target, 0, len(targetMap))
|
||||
|
||||
for quantile, epsilon := range targetMap {
|
||||
t := target{
|
||||
quantile: quantile,
|
||||
epsilon: epsilon,
|
||||
}
|
||||
targets = append(targets, t)
|
||||
}
|
||||
|
||||
return targets
|
||||
}
|
||||
|
||||
// Stream computes quantiles for a stream of float64s. It is not thread-safe by
|
||||
// design. Take care when using across multiple goroutines.
|
||||
type Stream struct {
|
||||
*stream
|
||||
b Samples
|
||||
sorted bool
|
||||
}
|
||||
|
||||
func newStream(ƒ invariant) *Stream {
|
||||
x := &stream{ƒ: ƒ}
|
||||
return &Stream{x, make(Samples, 0, 500), true}
|
||||
}
|
||||
|
||||
// Insert inserts v into the stream.
|
||||
func (s *Stream) Insert(v float64) {
|
||||
s.insert(Sample{Value: v, Width: 1})
|
||||
}
|
||||
|
||||
func (s *Stream) insert(sample Sample) {
|
||||
s.b = append(s.b, sample)
|
||||
s.sorted = false
|
||||
if len(s.b) == cap(s.b) {
|
||||
s.flush()
|
||||
}
|
||||
}
|
||||
|
||||
// Query returns the computed qth percentiles value. If s was created with
|
||||
// NewTargeted, and q is not in the set of quantiles provided a priori, Query
|
||||
// will return an unspecified result.
|
||||
func (s *Stream) Query(q float64) float64 {
|
||||
if !s.flushed() {
|
||||
// Fast path when there hasn't been enough data for a flush;
|
||||
// this also yields better accuracy for small sets of data.
|
||||
l := len(s.b)
|
||||
if l == 0 {
|
||||
return 0
|
||||
}
|
||||
i := int(math.Ceil(float64(l) * q))
|
||||
if i > 0 {
|
||||
i -= 1
|
||||
}
|
||||
s.maybeSort()
|
||||
return s.b[i].Value
|
||||
}
|
||||
s.flush()
|
||||
return s.stream.query(q)
|
||||
}
|
||||
|
||||
// Merge merges samples into the underlying streams samples. This is handy when
|
||||
// merging multiple streams from separate threads, database shards, etc.
|
||||
//
|
||||
// ATTENTION: This method is broken and does not yield correct results. The
|
||||
// underlying algorithm is not capable of merging streams correctly.
|
||||
func (s *Stream) Merge(samples Samples) {
|
||||
sort.Sort(samples)
|
||||
s.stream.merge(samples)
|
||||
}
|
||||
|
||||
// Reset reinitializes and clears the list reusing the samples buffer memory.
|
||||
func (s *Stream) Reset() {
|
||||
s.stream.reset()
|
||||
s.b = s.b[:0]
|
||||
}
|
||||
|
||||
// Samples returns stream samples held by s.
|
||||
func (s *Stream) Samples() Samples {
|
||||
if !s.flushed() {
|
||||
return s.b
|
||||
}
|
||||
s.flush()
|
||||
return s.stream.samples()
|
||||
}
|
||||
|
||||
// Count returns the total number of samples observed in the stream
|
||||
// since initialization.
|
||||
func (s *Stream) Count() int {
|
||||
return len(s.b) + s.stream.count()
|
||||
}
|
||||
|
||||
func (s *Stream) flush() {
|
||||
s.maybeSort()
|
||||
s.stream.merge(s.b)
|
||||
s.b = s.b[:0]
|
||||
}
|
||||
|
||||
func (s *Stream) maybeSort() {
|
||||
if !s.sorted {
|
||||
s.sorted = true
|
||||
sort.Sort(s.b)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) flushed() bool {
|
||||
return len(s.stream.l) > 0
|
||||
}
|
||||
|
||||
type stream struct {
|
||||
n float64
|
||||
l []Sample
|
||||
ƒ invariant
|
||||
}
|
||||
|
||||
func (s *stream) reset() {
|
||||
s.l = s.l[:0]
|
||||
s.n = 0
|
||||
}
|
||||
|
||||
func (s *stream) insert(v float64) {
|
||||
s.merge(Samples{{v, 1, 0}})
|
||||
}
|
||||
|
||||
func (s *stream) merge(samples Samples) {
|
||||
// TODO(beorn7): This tries to merge not only individual samples, but
|
||||
// whole summaries. The paper doesn't mention merging summaries at
|
||||
// all. Unittests show that the merging is inaccurate. Find out how to
|
||||
// do merges properly.
|
||||
var r float64
|
||||
i := 0
|
||||
for _, sample := range samples {
|
||||
for ; i < len(s.l); i++ {
|
||||
c := s.l[i]
|
||||
if c.Value > sample.Value {
|
||||
// Insert at position i.
|
||||
s.l = append(s.l, Sample{})
|
||||
copy(s.l[i+1:], s.l[i:])
|
||||
s.l[i] = Sample{
|
||||
sample.Value,
|
||||
sample.Width,
|
||||
math.Max(sample.Delta, math.Floor(s.ƒ(s, r))-1),
|
||||
// TODO(beorn7): How to calculate delta correctly?
|
||||
}
|
||||
i++
|
||||
goto inserted
|
||||
}
|
||||
r += c.Width
|
||||
}
|
||||
s.l = append(s.l, Sample{sample.Value, sample.Width, 0})
|
||||
i++
|
||||
inserted:
|
||||
s.n += sample.Width
|
||||
r += sample.Width
|
||||
}
|
||||
s.compress()
|
||||
}
|
||||
|
||||
func (s *stream) count() int {
|
||||
return int(s.n)
|
||||
}
|
||||
|
||||
func (s *stream) query(q float64) float64 {
|
||||
t := math.Ceil(q * s.n)
|
||||
t += math.Ceil(s.ƒ(s, t) / 2)
|
||||
p := s.l[0]
|
||||
var r float64
|
||||
for _, c := range s.l[1:] {
|
||||
r += p.Width
|
||||
if r+c.Width+c.Delta > t {
|
||||
return p.Value
|
||||
}
|
||||
p = c
|
||||
}
|
||||
return p.Value
|
||||
}
|
||||
|
||||
func (s *stream) compress() {
|
||||
if len(s.l) < 2 {
|
||||
return
|
||||
}
|
||||
x := s.l[len(s.l)-1]
|
||||
xi := len(s.l) - 1
|
||||
r := s.n - 1 - x.Width
|
||||
|
||||
for i := len(s.l) - 2; i >= 0; i-- {
|
||||
c := s.l[i]
|
||||
if c.Width+x.Width+x.Delta <= s.ƒ(s, r) {
|
||||
x.Width += c.Width
|
||||
s.l[xi] = x
|
||||
// Remove element at i.
|
||||
copy(s.l[i:], s.l[i+1:])
|
||||
s.l = s.l[:len(s.l)-1]
|
||||
xi -= 1
|
||||
} else {
|
||||
x = c
|
||||
xi = i
|
||||
}
|
||||
r -= c.Width
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stream) samples() Samples {
|
||||
samples := make(Samples, len(s.l))
|
||||
copy(samples, s.l)
|
||||
return samples
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
# http://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*_test.go]
|
||||
trim_trailing_whitespace = false
|
||||
Generated
-7
@@ -1,7 +0,0 @@
|
||||
testdata/conf_out.ini
|
||||
ini.sublime-project
|
||||
ini.sublime-workspace
|
||||
testdata/conf_reflect.ini
|
||||
.idea
|
||||
/.vscode
|
||||
.DS_Store
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
linters-settings:
|
||||
staticcheck:
|
||||
checks: [
|
||||
"all",
|
||||
"-SA1019" # There are valid use cases of strings.Title
|
||||
]
|
||||
nakedret:
|
||||
max-func-lines: 0 # Disallow any unnamed return statement
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- deadcode
|
||||
- errcheck
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- typecheck
|
||||
- unused
|
||||
- varcheck
|
||||
- nakedret
|
||||
- gofmt
|
||||
- rowserrcheck
|
||||
- unconvert
|
||||
- goimports
|
||||
- unparam
|
||||
-191
@@ -1,191 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, "control" means (i) the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
"submitted" means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution.
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source form
|
||||
of the Work, excluding those notices that do not pertain to any part of the
|
||||
Derivative Works; and
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions.
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
6. Trademarks.
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
|
||||
8. Limitation of Liability.
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability.
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets "[]" replaced with your own
|
||||
identifying information. (Don't include the brackets!) The text should be
|
||||
enclosed in the appropriate comment syntax for the file format. We also
|
||||
recommend that a file or class name and description of purpose be included on
|
||||
the same "printed page" as the copyright notice for easier identification within
|
||||
third-party archives.
|
||||
|
||||
Copyright 2014 Unknwon
|
||||
|
||||
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.
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
.PHONY: build test bench vet coverage
|
||||
|
||||
build: vet bench
|
||||
|
||||
test:
|
||||
go test -v -cover -race
|
||||
|
||||
bench:
|
||||
go test -v -cover -test.bench=. -test.benchmem
|
||||
|
||||
vet:
|
||||
go vet
|
||||
|
||||
coverage:
|
||||
go test -coverprofile=c.out && go tool cover -html=c.out && rm c.out
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
# INI
|
||||
|
||||
[](https://github.com/go-ini/ini/actions?query=branch%3Amain)
|
||||
[](https://codecov.io/gh/go-ini/ini)
|
||||
[](https://pkg.go.dev/github.com/go-ini/ini?tab=doc)
|
||||
[](https://sourcegraph.com/github.com/go-ini/ini)
|
||||
|
||||

|
||||
|
||||
Package ini provides INI file read and write functionality in Go.
|
||||
|
||||
## Features
|
||||
|
||||
- Load from multiple data sources(file, `[]byte`, `io.Reader` and `io.ReadCloser`) with overwrites.
|
||||
- Read with recursion values.
|
||||
- Read with parent-child sections.
|
||||
- Read with auto-increment key names.
|
||||
- Read with multiple-line values.
|
||||
- Read with tons of helper methods.
|
||||
- Read and convert values to Go types.
|
||||
- Read and **WRITE** comments of sections and keys.
|
||||
- Manipulate sections, keys and comments with ease.
|
||||
- Keep sections and keys in order as you parse and save.
|
||||
|
||||
## Installation
|
||||
|
||||
The minimum requirement of Go is **1.13**.
|
||||
|
||||
```sh
|
||||
$ go get gopkg.in/ini.v1
|
||||
```
|
||||
|
||||
Please add `-u` flag to update in the future.
|
||||
|
||||
## Getting Help
|
||||
|
||||
- [Getting Started](https://ini.unknwon.io/docs/intro/getting_started)
|
||||
- [API Documentation](https://gowalker.org/gopkg.in/ini.v1)
|
||||
- 中国大陆镜像:https://ini.unknwon.cn
|
||||
|
||||
## License
|
||||
|
||||
This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
coverage:
|
||||
range: "60...95"
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
threshold: 1%
|
||||
informational: true
|
||||
patch:
|
||||
defualt:
|
||||
only_pulls: true
|
||||
informational: true
|
||||
|
||||
comment:
|
||||
layout: 'diff'
|
||||
|
||||
github_checks: false
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
// Copyright 2019 Unknwon
|
||||
//
|
||||
// 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 ini
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
_ dataSource = (*sourceFile)(nil)
|
||||
_ dataSource = (*sourceData)(nil)
|
||||
_ dataSource = (*sourceReadCloser)(nil)
|
||||
)
|
||||
|
||||
// dataSource is an interface that returns object which can be read and closed.
|
||||
type dataSource interface {
|
||||
ReadCloser() (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
// sourceFile represents an object that contains content on the local file system.
|
||||
type sourceFile struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) {
|
||||
return os.Open(s.name)
|
||||
}
|
||||
|
||||
// sourceData represents an object that contains content in memory.
|
||||
type sourceData struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (s *sourceData) ReadCloser() (io.ReadCloser, error) {
|
||||
return ioutil.NopCloser(bytes.NewReader(s.data)), nil
|
||||
}
|
||||
|
||||
// sourceReadCloser represents an input stream with Close method.
|
||||
type sourceReadCloser struct {
|
||||
reader io.ReadCloser
|
||||
}
|
||||
|
||||
func (s *sourceReadCloser) ReadCloser() (io.ReadCloser, error) {
|
||||
return s.reader, nil
|
||||
}
|
||||
|
||||
func parseDataSource(source interface{}) (dataSource, error) {
|
||||
switch s := source.(type) {
|
||||
case string:
|
||||
return sourceFile{s}, nil
|
||||
case []byte:
|
||||
return &sourceData{s}, nil
|
||||
case io.ReadCloser:
|
||||
return &sourceReadCloser{s}, nil
|
||||
case io.Reader:
|
||||
return &sourceReadCloser{ioutil.NopCloser(s)}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("error parsing data source: unknown type %q", s)
|
||||
}
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
// Copyright 2019 Unknwon
|
||||
//
|
||||
// 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 ini
|
||||
|
||||
var (
|
||||
// Deprecated: Use "DefaultSection" instead.
|
||||
DEFAULT_SECTION = DefaultSection
|
||||
// Deprecated: AllCapsUnderscore converts to format ALL_CAPS_UNDERSCORE.
|
||||
AllCapsUnderscore = SnackCase
|
||||
)
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
// Copyright 2016 Unknwon
|
||||
//
|
||||
// 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 ini
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ErrDelimiterNotFound indicates the error type of no delimiter is found which there should be one.
|
||||
type ErrDelimiterNotFound struct {
|
||||
Line string
|
||||
}
|
||||
|
||||
// IsErrDelimiterNotFound returns true if the given error is an instance of ErrDelimiterNotFound.
|
||||
func IsErrDelimiterNotFound(err error) bool {
|
||||
_, ok := err.(ErrDelimiterNotFound)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrDelimiterNotFound) Error() string {
|
||||
return fmt.Sprintf("key-value delimiter not found: %s", err.Line)
|
||||
}
|
||||
|
||||
// ErrEmptyKeyName indicates the error type of no key name is found which there should be one.
|
||||
type ErrEmptyKeyName struct {
|
||||
Line string
|
||||
}
|
||||
|
||||
// IsErrEmptyKeyName returns true if the given error is an instance of ErrEmptyKeyName.
|
||||
func IsErrEmptyKeyName(err error) bool {
|
||||
_, ok := err.(ErrEmptyKeyName)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrEmptyKeyName) Error() string {
|
||||
return fmt.Sprintf("empty key name: %s", err.Line)
|
||||
}
|
||||
-541
@@ -1,541 +0,0 @@
|
||||
// Copyright 2017 Unknwon
|
||||
//
|
||||
// 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 ini
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// File represents a combination of one or more INI files in memory.
|
||||
type File struct {
|
||||
options LoadOptions
|
||||
dataSources []dataSource
|
||||
|
||||
// Should make things safe, but sometimes doesn't matter.
|
||||
BlockMode bool
|
||||
lock sync.RWMutex
|
||||
|
||||
// To keep data in order.
|
||||
sectionList []string
|
||||
// To keep track of the index of a section with same name.
|
||||
// This meta list is only used with non-unique section names are allowed.
|
||||
sectionIndexes []int
|
||||
|
||||
// Actual data is stored here.
|
||||
sections map[string][]*Section
|
||||
|
||||
NameMapper
|
||||
ValueMapper
|
||||
}
|
||||
|
||||
// newFile initializes File object with given data sources.
|
||||
func newFile(dataSources []dataSource, opts LoadOptions) *File {
|
||||
if len(opts.KeyValueDelimiters) == 0 {
|
||||
opts.KeyValueDelimiters = "=:"
|
||||
}
|
||||
if len(opts.KeyValueDelimiterOnWrite) == 0 {
|
||||
opts.KeyValueDelimiterOnWrite = "="
|
||||
}
|
||||
if len(opts.ChildSectionDelimiter) == 0 {
|
||||
opts.ChildSectionDelimiter = "."
|
||||
}
|
||||
|
||||
return &File{
|
||||
BlockMode: true,
|
||||
dataSources: dataSources,
|
||||
sections: make(map[string][]*Section),
|
||||
options: opts,
|
||||
}
|
||||
}
|
||||
|
||||
// Empty returns an empty file object.
|
||||
func Empty(opts ...LoadOptions) *File {
|
||||
var opt LoadOptions
|
||||
if len(opts) > 0 {
|
||||
opt = opts[0]
|
||||
}
|
||||
|
||||
// Ignore error here, we are sure our data is good.
|
||||
f, _ := LoadSources(opt, []byte(""))
|
||||
return f
|
||||
}
|
||||
|
||||
// NewSection creates a new section.
|
||||
func (f *File) NewSection(name string) (*Section, error) {
|
||||
if len(name) == 0 {
|
||||
return nil, errors.New("empty section name")
|
||||
}
|
||||
|
||||
if (f.options.Insensitive || f.options.InsensitiveSections) && name != DefaultSection {
|
||||
name = strings.ToLower(name)
|
||||
}
|
||||
|
||||
if f.BlockMode {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
}
|
||||
|
||||
if !f.options.AllowNonUniqueSections && inSlice(name, f.sectionList) {
|
||||
return f.sections[name][0], nil
|
||||
}
|
||||
|
||||
f.sectionList = append(f.sectionList, name)
|
||||
|
||||
// NOTE: Append to indexes must happen before appending to sections,
|
||||
// otherwise index will have off-by-one problem.
|
||||
f.sectionIndexes = append(f.sectionIndexes, len(f.sections[name]))
|
||||
|
||||
sec := newSection(f, name)
|
||||
f.sections[name] = append(f.sections[name], sec)
|
||||
|
||||
return sec, nil
|
||||
}
|
||||
|
||||
// NewRawSection creates a new section with an unparseable body.
|
||||
func (f *File) NewRawSection(name, body string) (*Section, error) {
|
||||
section, err := f.NewSection(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
section.isRawSection = true
|
||||
section.rawBody = body
|
||||
return section, nil
|
||||
}
|
||||
|
||||
// NewSections creates a list of sections.
|
||||
func (f *File) NewSections(names ...string) (err error) {
|
||||
for _, name := range names {
|
||||
if _, err = f.NewSection(name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSection returns section by given name.
|
||||
func (f *File) GetSection(name string) (*Section, error) {
|
||||
secs, err := f.SectionsByName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return secs[0], err
|
||||
}
|
||||
|
||||
// HasSection returns true if the file contains a section with given name.
|
||||
func (f *File) HasSection(name string) bool {
|
||||
section, _ := f.GetSection(name)
|
||||
return section != nil
|
||||
}
|
||||
|
||||
// SectionsByName returns all sections with given name.
|
||||
func (f *File) SectionsByName(name string) ([]*Section, error) {
|
||||
if len(name) == 0 {
|
||||
name = DefaultSection
|
||||
}
|
||||
if f.options.Insensitive || f.options.InsensitiveSections {
|
||||
name = strings.ToLower(name)
|
||||
}
|
||||
|
||||
if f.BlockMode {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
}
|
||||
|
||||
secs := f.sections[name]
|
||||
if len(secs) == 0 {
|
||||
return nil, fmt.Errorf("section %q does not exist", name)
|
||||
}
|
||||
|
||||
return secs, nil
|
||||
}
|
||||
|
||||
// Section assumes named section exists and returns a zero-value when not.
|
||||
func (f *File) Section(name string) *Section {
|
||||
sec, err := f.GetSection(name)
|
||||
if err != nil {
|
||||
if name == "" {
|
||||
name = DefaultSection
|
||||
}
|
||||
sec, _ = f.NewSection(name)
|
||||
return sec
|
||||
}
|
||||
return sec
|
||||
}
|
||||
|
||||
// SectionWithIndex assumes named section exists and returns a new section when not.
|
||||
func (f *File) SectionWithIndex(name string, index int) *Section {
|
||||
secs, err := f.SectionsByName(name)
|
||||
if err != nil || len(secs) <= index {
|
||||
// NOTE: It's OK here because the only possible error is empty section name,
|
||||
// but if it's empty, this piece of code won't be executed.
|
||||
newSec, _ := f.NewSection(name)
|
||||
return newSec
|
||||
}
|
||||
|
||||
return secs[index]
|
||||
}
|
||||
|
||||
// Sections returns a list of Section stored in the current instance.
|
||||
func (f *File) Sections() []*Section {
|
||||
if f.BlockMode {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
}
|
||||
|
||||
sections := make([]*Section, len(f.sectionList))
|
||||
for i, name := range f.sectionList {
|
||||
sections[i] = f.sections[name][f.sectionIndexes[i]]
|
||||
}
|
||||
return sections
|
||||
}
|
||||
|
||||
// ChildSections returns a list of child sections of given section name.
|
||||
func (f *File) ChildSections(name string) []*Section {
|
||||
return f.Section(name).ChildSections()
|
||||
}
|
||||
|
||||
// SectionStrings returns list of section names.
|
||||
func (f *File) SectionStrings() []string {
|
||||
list := make([]string, len(f.sectionList))
|
||||
copy(list, f.sectionList)
|
||||
return list
|
||||
}
|
||||
|
||||
// DeleteSection deletes a section or all sections with given name.
|
||||
func (f *File) DeleteSection(name string) {
|
||||
secs, err := f.SectionsByName(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < len(secs); i++ {
|
||||
// For non-unique sections, it is always needed to remove the first one so
|
||||
// in the next iteration, the subsequent section continue having index 0.
|
||||
// Ignoring the error as index 0 never returns an error.
|
||||
_ = f.DeleteSectionWithIndex(name, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteSectionWithIndex deletes a section with given name and index.
|
||||
func (f *File) DeleteSectionWithIndex(name string, index int) error {
|
||||
if !f.options.AllowNonUniqueSections && index != 0 {
|
||||
return fmt.Errorf("delete section with non-zero index is only allowed when non-unique sections is enabled")
|
||||
}
|
||||
|
||||
if len(name) == 0 {
|
||||
name = DefaultSection
|
||||
}
|
||||
if f.options.Insensitive || f.options.InsensitiveSections {
|
||||
name = strings.ToLower(name)
|
||||
}
|
||||
|
||||
if f.BlockMode {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
}
|
||||
|
||||
// Count occurrences of the sections
|
||||
occurrences := 0
|
||||
|
||||
sectionListCopy := make([]string, len(f.sectionList))
|
||||
copy(sectionListCopy, f.sectionList)
|
||||
|
||||
for i, s := range sectionListCopy {
|
||||
if s != name {
|
||||
continue
|
||||
}
|
||||
|
||||
if occurrences == index {
|
||||
if len(f.sections[name]) <= 1 {
|
||||
delete(f.sections, name) // The last one in the map
|
||||
} else {
|
||||
f.sections[name] = append(f.sections[name][:index], f.sections[name][index+1:]...)
|
||||
}
|
||||
|
||||
// Fix section lists
|
||||
f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
|
||||
f.sectionIndexes = append(f.sectionIndexes[:i], f.sectionIndexes[i+1:]...)
|
||||
|
||||
} else if occurrences > index {
|
||||
// Fix the indices of all following sections with this name.
|
||||
f.sectionIndexes[i-1]--
|
||||
}
|
||||
|
||||
occurrences++
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *File) reload(s dataSource) error {
|
||||
r, err := s.ReadCloser()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
return f.parse(r)
|
||||
}
|
||||
|
||||
// Reload reloads and parses all data sources.
|
||||
func (f *File) Reload() (err error) {
|
||||
for _, s := range f.dataSources {
|
||||
if err = f.reload(s); err != nil {
|
||||
// In loose mode, we create an empty default section for nonexistent files.
|
||||
if os.IsNotExist(err) && f.options.Loose {
|
||||
_ = f.parse(bytes.NewBuffer(nil))
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
if f.options.ShortCircuit {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Append appends one or more data sources and reloads automatically.
|
||||
func (f *File) Append(source interface{}, others ...interface{}) error {
|
||||
ds, err := parseDataSource(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.dataSources = append(f.dataSources, ds)
|
||||
for _, s := range others {
|
||||
ds, err = parseDataSource(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.dataSources = append(f.dataSources, ds)
|
||||
}
|
||||
return f.Reload()
|
||||
}
|
||||
|
||||
func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
|
||||
equalSign := DefaultFormatLeft + f.options.KeyValueDelimiterOnWrite + DefaultFormatRight
|
||||
|
||||
if PrettyFormat || PrettyEqual {
|
||||
equalSign = fmt.Sprintf(" %s ", f.options.KeyValueDelimiterOnWrite)
|
||||
}
|
||||
|
||||
// Use buffer to make sure target is safe until finish encoding.
|
||||
buf := bytes.NewBuffer(nil)
|
||||
lastSectionIdx := len(f.sectionList) - 1
|
||||
for i, sname := range f.sectionList {
|
||||
sec := f.SectionWithIndex(sname, f.sectionIndexes[i])
|
||||
if len(sec.Comment) > 0 {
|
||||
// Support multiline comments
|
||||
lines := strings.Split(sec.Comment, LineBreak)
|
||||
for i := range lines {
|
||||
if lines[i][0] != '#' && lines[i][0] != ';' {
|
||||
lines[i] = "; " + lines[i]
|
||||
} else {
|
||||
lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
|
||||
}
|
||||
|
||||
if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if i > 0 || DefaultHeader || (i == 0 && strings.ToUpper(sec.name) != DefaultSection) {
|
||||
if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// Write nothing if default section is empty
|
||||
if len(sec.keyList) == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
isLastSection := i == lastSectionIdx
|
||||
if sec.isRawSection {
|
||||
if _, err := buf.WriteString(sec.rawBody); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if PrettySection && !isLastSection {
|
||||
// Put a line between sections
|
||||
if _, err := buf.WriteString(LineBreak); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Count and generate alignment length and buffer spaces using the
|
||||
// longest key. Keys may be modified if they contain certain characters so
|
||||
// we need to take that into account in our calculation.
|
||||
alignLength := 0
|
||||
if PrettyFormat {
|
||||
for _, kname := range sec.keyList {
|
||||
keyLength := len(kname)
|
||||
// First case will surround key by ` and second by """
|
||||
if strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters) {
|
||||
keyLength += 2
|
||||
} else if strings.Contains(kname, "`") {
|
||||
keyLength += 6
|
||||
}
|
||||
|
||||
if keyLength > alignLength {
|
||||
alignLength = keyLength
|
||||
}
|
||||
}
|
||||
}
|
||||
alignSpaces := bytes.Repeat([]byte(" "), alignLength)
|
||||
|
||||
KeyList:
|
||||
for _, kname := range sec.keyList {
|
||||
key := sec.Key(kname)
|
||||
if len(key.Comment) > 0 {
|
||||
if len(indent) > 0 && sname != DefaultSection {
|
||||
buf.WriteString(indent)
|
||||
}
|
||||
|
||||
// Support multiline comments
|
||||
lines := strings.Split(key.Comment, LineBreak)
|
||||
for i := range lines {
|
||||
if lines[i][0] != '#' && lines[i][0] != ';' {
|
||||
lines[i] = "; " + strings.TrimSpace(lines[i])
|
||||
} else {
|
||||
lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
|
||||
}
|
||||
|
||||
if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(indent) > 0 && sname != DefaultSection {
|
||||
buf.WriteString(indent)
|
||||
}
|
||||
|
||||
switch {
|
||||
case key.isAutoIncrement:
|
||||
kname = "-"
|
||||
case strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters):
|
||||
kname = "`" + kname + "`"
|
||||
case strings.Contains(kname, "`"):
|
||||
kname = `"""` + kname + `"""`
|
||||
}
|
||||
|
||||
writeKeyValue := func(val string) (bool, error) {
|
||||
if _, err := buf.WriteString(kname); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if key.isBooleanType {
|
||||
buf.WriteString(LineBreak)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Write out alignment spaces before "=" sign
|
||||
if PrettyFormat {
|
||||
buf.Write(alignSpaces[:alignLength-len(kname)])
|
||||
}
|
||||
|
||||
// In case key value contains "\n", "`", "\"", "#" or ";"
|
||||
if strings.ContainsAny(val, "\n`") {
|
||||
val = `"""` + val + `"""`
|
||||
} else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") {
|
||||
val = "`" + val + "`"
|
||||
} else if len(strings.TrimSpace(val)) != len(val) {
|
||||
val = `"` + val + `"`
|
||||
}
|
||||
if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
shadows := key.ValueWithShadows()
|
||||
if len(shadows) == 0 {
|
||||
if _, err := writeKeyValue(""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, val := range shadows {
|
||||
exitLoop, err := writeKeyValue(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if exitLoop {
|
||||
continue KeyList
|
||||
}
|
||||
}
|
||||
|
||||
for _, val := range key.nestedValues {
|
||||
if _, err := buf.WriteString(indent + " " + val + LineBreak); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if PrettySection && !isLastSection {
|
||||
// Put a line between sections
|
||||
if _, err := buf.WriteString(LineBreak); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// WriteToIndent writes content into io.Writer with given indention.
|
||||
// If PrettyFormat has been set to be true,
|
||||
// it will align "=" sign with spaces under each section.
|
||||
func (f *File) WriteToIndent(w io.Writer, indent string) (int64, error) {
|
||||
buf, err := f.writeToBuffer(indent)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return buf.WriteTo(w)
|
||||
}
|
||||
|
||||
// WriteTo writes file content into io.Writer.
|
||||
func (f *File) WriteTo(w io.Writer) (int64, error) {
|
||||
return f.WriteToIndent(w, "")
|
||||
}
|
||||
|
||||
// SaveToIndent writes content to file system with given value indention.
|
||||
func (f *File) SaveToIndent(filename, indent string) error {
|
||||
// Note: Because we are truncating with os.Create,
|
||||
// so it's safer to save to a temporary file location and rename after done.
|
||||
buf, err := f.writeToBuffer(indent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(filename, buf.Bytes(), 0666)
|
||||
}
|
||||
|
||||
// SaveTo writes content to file system.
|
||||
func (f *File) SaveTo(filename string) error {
|
||||
return f.SaveToIndent(filename, "")
|
||||
}
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
// Copyright 2019 Unknwon
|
||||
//
|
||||
// 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 ini
|
||||
|
||||
func inSlice(str string, s []string) bool {
|
||||
for _, v := range s {
|
||||
if str == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
-176
@@ -1,176 +0,0 @@
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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 ini provides INI file read and write functionality in Go.
|
||||
package ini
|
||||
|
||||
import (
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// Maximum allowed depth when recursively substituing variable names.
|
||||
depthValues = 99
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultSection is the name of default section. You can use this var or the string literal.
|
||||
// In most of cases, an empty string is all you need to access the section.
|
||||
DefaultSection = "DEFAULT"
|
||||
|
||||
// LineBreak is the delimiter to determine or compose a new line.
|
||||
// This variable will be changed to "\r\n" automatically on Windows at package init time.
|
||||
LineBreak = "\n"
|
||||
|
||||
// Variable regexp pattern: %(variable)s
|
||||
varPattern = regexp.MustCompile(`%\(([^)]+)\)s`)
|
||||
|
||||
// DefaultHeader explicitly writes default section header.
|
||||
DefaultHeader = false
|
||||
|
||||
// PrettySection indicates whether to put a line between sections.
|
||||
PrettySection = true
|
||||
// PrettyFormat indicates whether to align "=" sign with spaces to produce pretty output
|
||||
// or reduce all possible spaces for compact format.
|
||||
PrettyFormat = true
|
||||
// PrettyEqual places spaces around "=" sign even when PrettyFormat is false.
|
||||
PrettyEqual = false
|
||||
// DefaultFormatLeft places custom spaces on the left when PrettyFormat and PrettyEqual are both disabled.
|
||||
DefaultFormatLeft = ""
|
||||
// DefaultFormatRight places custom spaces on the right when PrettyFormat and PrettyEqual are both disabled.
|
||||
DefaultFormatRight = ""
|
||||
)
|
||||
|
||||
var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test")
|
||||
|
||||
func init() {
|
||||
if runtime.GOOS == "windows" && !inTest {
|
||||
LineBreak = "\r\n"
|
||||
}
|
||||
}
|
||||
|
||||
// LoadOptions contains all customized options used for load data source(s).
|
||||
type LoadOptions struct {
|
||||
// Loose indicates whether the parser should ignore nonexistent files or return error.
|
||||
Loose bool
|
||||
// Insensitive indicates whether the parser forces all section and key names to lowercase.
|
||||
Insensitive bool
|
||||
// InsensitiveSections indicates whether the parser forces all section to lowercase.
|
||||
InsensitiveSections bool
|
||||
// InsensitiveKeys indicates whether the parser forces all key names to lowercase.
|
||||
InsensitiveKeys bool
|
||||
// IgnoreContinuation indicates whether to ignore continuation lines while parsing.
|
||||
IgnoreContinuation bool
|
||||
// IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value.
|
||||
IgnoreInlineComment bool
|
||||
// SkipUnrecognizableLines indicates whether to skip unrecognizable lines that do not conform to key/value pairs.
|
||||
SkipUnrecognizableLines bool
|
||||
// ShortCircuit indicates whether to ignore other configuration sources after loaded the first available configuration source.
|
||||
ShortCircuit bool
|
||||
// AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
|
||||
// This type of keys are mostly used in my.cnf.
|
||||
AllowBooleanKeys bool
|
||||
// AllowShadows indicates whether to keep track of keys with same name under same section.
|
||||
AllowShadows bool
|
||||
// AllowNestedValues indicates whether to allow AWS-like nested values.
|
||||
// Docs: http://docs.aws.amazon.com/cli/latest/topic/config-vars.html#nested-values
|
||||
AllowNestedValues bool
|
||||
// AllowPythonMultilineValues indicates whether to allow Python-like multi-line values.
|
||||
// Docs: https://docs.python.org/3/library/configparser.html#supported-ini-file-structure
|
||||
// Relevant quote: Values can also span multiple lines, as long as they are indented deeper
|
||||
// than the first line of the value.
|
||||
AllowPythonMultilineValues bool
|
||||
// SpaceBeforeInlineComment indicates whether to allow comment symbols (\# and \;) inside value.
|
||||
// Docs: https://docs.python.org/2/library/configparser.html
|
||||
// Quote: Comments may appear on their own in an otherwise empty line, or may be entered in lines holding values or section names.
|
||||
// In the latter case, they need to be preceded by a whitespace character to be recognized as a comment.
|
||||
SpaceBeforeInlineComment bool
|
||||
// UnescapeValueDoubleQuotes indicates whether to unescape double quotes inside value to regular format
|
||||
// when value is surrounded by double quotes, e.g. key="a \"value\"" => key=a "value"
|
||||
UnescapeValueDoubleQuotes bool
|
||||
// UnescapeValueCommentSymbols indicates to unescape comment symbols (\# and \;) inside value to regular format
|
||||
// when value is NOT surrounded by any quotes.
|
||||
// Note: UNSTABLE, behavior might change to only unescape inside double quotes but may noy necessary at all.
|
||||
UnescapeValueCommentSymbols bool
|
||||
// UnparseableSections stores a list of blocks that are allowed with raw content which do not otherwise
|
||||
// conform to key/value pairs. Specify the names of those blocks here.
|
||||
UnparseableSections []string
|
||||
// KeyValueDelimiters is the sequence of delimiters that are used to separate key and value. By default, it is "=:".
|
||||
KeyValueDelimiters string
|
||||
// KeyValueDelimiterOnWrite is the delimiter that are used to separate key and value output. By default, it is "=".
|
||||
KeyValueDelimiterOnWrite string
|
||||
// ChildSectionDelimiter is the delimiter that is used to separate child sections. By default, it is ".".
|
||||
ChildSectionDelimiter string
|
||||
// PreserveSurroundedQuote indicates whether to preserve surrounded quote (single and double quotes).
|
||||
PreserveSurroundedQuote bool
|
||||
// DebugFunc is called to collect debug information (currently only useful to debug parsing Python-style multiline values).
|
||||
DebugFunc DebugFunc
|
||||
// ReaderBufferSize is the buffer size of the reader in bytes.
|
||||
ReaderBufferSize int
|
||||
// AllowNonUniqueSections indicates whether to allow sections with the same name multiple times.
|
||||
AllowNonUniqueSections bool
|
||||
// AllowDuplicateShadowValues indicates whether values for shadowed keys should be deduplicated.
|
||||
AllowDuplicateShadowValues bool
|
||||
}
|
||||
|
||||
// DebugFunc is the type of function called to log parse events.
|
||||
type DebugFunc func(message string)
|
||||
|
||||
// LoadSources allows caller to apply customized options for loading from data source(s).
|
||||
func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) {
|
||||
sources := make([]dataSource, len(others)+1)
|
||||
sources[0], err = parseDataSource(source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range others {
|
||||
sources[i+1], err = parseDataSource(others[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
f := newFile(sources, opts)
|
||||
if err = f.Reload(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// Load loads and parses from INI data sources.
|
||||
// Arguments can be mixed of file name with string type, or raw data in []byte.
|
||||
// It will return error if list contains nonexistent files.
|
||||
func Load(source interface{}, others ...interface{}) (*File, error) {
|
||||
return LoadSources(LoadOptions{}, source, others...)
|
||||
}
|
||||
|
||||
// LooseLoad has exactly same functionality as Load function
|
||||
// except it ignores nonexistent files instead of returning error.
|
||||
func LooseLoad(source interface{}, others ...interface{}) (*File, error) {
|
||||
return LoadSources(LoadOptions{Loose: true}, source, others...)
|
||||
}
|
||||
|
||||
// InsensitiveLoad has exactly same functionality as Load function
|
||||
// except it forces all section and key names to be lowercased.
|
||||
func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
|
||||
return LoadSources(LoadOptions{Insensitive: true}, source, others...)
|
||||
}
|
||||
|
||||
// ShadowLoad has exactly same functionality as Load function
|
||||
// except it allows have shadow keys.
|
||||
func ShadowLoad(source interface{}, others ...interface{}) (*File, error) {
|
||||
return LoadSources(LoadOptions{AllowShadows: true}, source, others...)
|
||||
}
|
||||
-837
@@ -1,837 +0,0 @@
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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 ini
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Key represents a key under a section.
|
||||
type Key struct {
|
||||
s *Section
|
||||
Comment string
|
||||
name string
|
||||
value string
|
||||
isAutoIncrement bool
|
||||
isBooleanType bool
|
||||
|
||||
isShadow bool
|
||||
shadows []*Key
|
||||
|
||||
nestedValues []string
|
||||
}
|
||||
|
||||
// newKey simply return a key object with given values.
|
||||
func newKey(s *Section, name, val string) *Key {
|
||||
return &Key{
|
||||
s: s,
|
||||
name: name,
|
||||
value: val,
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Key) addShadow(val string) error {
|
||||
if k.isShadow {
|
||||
return errors.New("cannot add shadow to another shadow key")
|
||||
} else if k.isAutoIncrement || k.isBooleanType {
|
||||
return errors.New("cannot add shadow to auto-increment or boolean key")
|
||||
}
|
||||
|
||||
if !k.s.f.options.AllowDuplicateShadowValues {
|
||||
// Deduplicate shadows based on their values.
|
||||
if k.value == val {
|
||||
return nil
|
||||
}
|
||||
for i := range k.shadows {
|
||||
if k.shadows[i].value == val {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shadow := newKey(k.s, k.name, val)
|
||||
shadow.isShadow = true
|
||||
k.shadows = append(k.shadows, shadow)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddShadow adds a new shadow key to itself.
|
||||
func (k *Key) AddShadow(val string) error {
|
||||
if !k.s.f.options.AllowShadows {
|
||||
return errors.New("shadow key is not allowed")
|
||||
}
|
||||
return k.addShadow(val)
|
||||
}
|
||||
|
||||
func (k *Key) addNestedValue(val string) error {
|
||||
if k.isAutoIncrement || k.isBooleanType {
|
||||
return errors.New("cannot add nested value to auto-increment or boolean key")
|
||||
}
|
||||
|
||||
k.nestedValues = append(k.nestedValues, val)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddNestedValue adds a nested value to the key.
|
||||
func (k *Key) AddNestedValue(val string) error {
|
||||
if !k.s.f.options.AllowNestedValues {
|
||||
return errors.New("nested value is not allowed")
|
||||
}
|
||||
return k.addNestedValue(val)
|
||||
}
|
||||
|
||||
// ValueMapper represents a mapping function for values, e.g. os.ExpandEnv
|
||||
type ValueMapper func(string) string
|
||||
|
||||
// Name returns name of key.
|
||||
func (k *Key) Name() string {
|
||||
return k.name
|
||||
}
|
||||
|
||||
// Value returns raw value of key for performance purpose.
|
||||
func (k *Key) Value() string {
|
||||
return k.value
|
||||
}
|
||||
|
||||
// ValueWithShadows returns raw values of key and its shadows if any. Shadow
|
||||
// keys with empty values are ignored from the returned list.
|
||||
func (k *Key) ValueWithShadows() []string {
|
||||
if len(k.shadows) == 0 {
|
||||
if k.value == "" {
|
||||
return []string{}
|
||||
}
|
||||
return []string{k.value}
|
||||
}
|
||||
|
||||
vals := make([]string, 0, len(k.shadows)+1)
|
||||
if k.value != "" {
|
||||
vals = append(vals, k.value)
|
||||
}
|
||||
for _, s := range k.shadows {
|
||||
if s.value != "" {
|
||||
vals = append(vals, s.value)
|
||||
}
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
// NestedValues returns nested values stored in the key.
|
||||
// It is possible returned value is nil if no nested values stored in the key.
|
||||
func (k *Key) NestedValues() []string {
|
||||
return k.nestedValues
|
||||
}
|
||||
|
||||
// transformValue takes a raw value and transforms to its final string.
|
||||
func (k *Key) transformValue(val string) string {
|
||||
if k.s.f.ValueMapper != nil {
|
||||
val = k.s.f.ValueMapper(val)
|
||||
}
|
||||
|
||||
// Fail-fast if no indicate char found for recursive value
|
||||
if !strings.Contains(val, "%") {
|
||||
return val
|
||||
}
|
||||
for i := 0; i < depthValues; i++ {
|
||||
vr := varPattern.FindString(val)
|
||||
if len(vr) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Take off leading '%(' and trailing ')s'.
|
||||
noption := vr[2 : len(vr)-2]
|
||||
|
||||
// Search in the same section.
|
||||
// If not found or found the key itself, then search again in default section.
|
||||
nk, err := k.s.GetKey(noption)
|
||||
if err != nil || k == nk {
|
||||
nk, _ = k.s.f.Section("").GetKey(noption)
|
||||
if nk == nil {
|
||||
// Stop when no results found in the default section,
|
||||
// and returns the value as-is.
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Substitute by new value and take off leading '%(' and trailing ')s'.
|
||||
val = strings.Replace(val, vr, nk.value, -1)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// String returns string representation of value.
|
||||
func (k *Key) String() string {
|
||||
return k.transformValue(k.value)
|
||||
}
|
||||
|
||||
// Validate accepts a validate function which can
|
||||
// return modifed result as key value.
|
||||
func (k *Key) Validate(fn func(string) string) string {
|
||||
return fn(k.String())
|
||||
}
|
||||
|
||||
// parseBool returns the boolean value represented by the string.
|
||||
//
|
||||
// It accepts 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On,
|
||||
// 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off.
|
||||
// Any other value returns an error.
|
||||
func parseBool(str string) (value bool, err error) {
|
||||
switch str {
|
||||
case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "y", "ON", "on", "On":
|
||||
return true, nil
|
||||
case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "n", "OFF", "off", "Off":
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("parsing \"%s\": invalid syntax", str)
|
||||
}
|
||||
|
||||
// Bool returns bool type value.
|
||||
func (k *Key) Bool() (bool, error) {
|
||||
return parseBool(k.String())
|
||||
}
|
||||
|
||||
// Float64 returns float64 type value.
|
||||
func (k *Key) Float64() (float64, error) {
|
||||
return strconv.ParseFloat(k.String(), 64)
|
||||
}
|
||||
|
||||
// Int returns int type value.
|
||||
func (k *Key) Int() (int, error) {
|
||||
v, err := strconv.ParseInt(k.String(), 0, 64)
|
||||
return int(v), err
|
||||
}
|
||||
|
||||
// Int64 returns int64 type value.
|
||||
func (k *Key) Int64() (int64, error) {
|
||||
return strconv.ParseInt(k.String(), 0, 64)
|
||||
}
|
||||
|
||||
// Uint returns uint type valued.
|
||||
func (k *Key) Uint() (uint, error) {
|
||||
u, e := strconv.ParseUint(k.String(), 0, 64)
|
||||
return uint(u), e
|
||||
}
|
||||
|
||||
// Uint64 returns uint64 type value.
|
||||
func (k *Key) Uint64() (uint64, error) {
|
||||
return strconv.ParseUint(k.String(), 0, 64)
|
||||
}
|
||||
|
||||
// Duration returns time.Duration type value.
|
||||
func (k *Key) Duration() (time.Duration, error) {
|
||||
return time.ParseDuration(k.String())
|
||||
}
|
||||
|
||||
// TimeFormat parses with given format and returns time.Time type value.
|
||||
func (k *Key) TimeFormat(format string) (time.Time, error) {
|
||||
return time.Parse(format, k.String())
|
||||
}
|
||||
|
||||
// Time parses with RFC3339 format and returns time.Time type value.
|
||||
func (k *Key) Time() (time.Time, error) {
|
||||
return k.TimeFormat(time.RFC3339)
|
||||
}
|
||||
|
||||
// MustString returns default value if key value is empty.
|
||||
func (k *Key) MustString(defaultVal string) string {
|
||||
val := k.String()
|
||||
if len(val) == 0 {
|
||||
k.value = defaultVal
|
||||
return defaultVal
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// MustBool always returns value without error,
|
||||
// it returns false if error occurs.
|
||||
func (k *Key) MustBool(defaultVal ...bool) bool {
|
||||
val, err := k.Bool()
|
||||
if len(defaultVal) > 0 && err != nil {
|
||||
k.value = strconv.FormatBool(defaultVal[0])
|
||||
return defaultVal[0]
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// MustFloat64 always returns value without error,
|
||||
// it returns 0.0 if error occurs.
|
||||
func (k *Key) MustFloat64(defaultVal ...float64) float64 {
|
||||
val, err := k.Float64()
|
||||
if len(defaultVal) > 0 && err != nil {
|
||||
k.value = strconv.FormatFloat(defaultVal[0], 'f', -1, 64)
|
||||
return defaultVal[0]
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// MustInt always returns value without error,
|
||||
// it returns 0 if error occurs.
|
||||
func (k *Key) MustInt(defaultVal ...int) int {
|
||||
val, err := k.Int()
|
||||
if len(defaultVal) > 0 && err != nil {
|
||||
k.value = strconv.FormatInt(int64(defaultVal[0]), 10)
|
||||
return defaultVal[0]
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// MustInt64 always returns value without error,
|
||||
// it returns 0 if error occurs.
|
||||
func (k *Key) MustInt64(defaultVal ...int64) int64 {
|
||||
val, err := k.Int64()
|
||||
if len(defaultVal) > 0 && err != nil {
|
||||
k.value = strconv.FormatInt(defaultVal[0], 10)
|
||||
return defaultVal[0]
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// MustUint always returns value without error,
|
||||
// it returns 0 if error occurs.
|
||||
func (k *Key) MustUint(defaultVal ...uint) uint {
|
||||
val, err := k.Uint()
|
||||
if len(defaultVal) > 0 && err != nil {
|
||||
k.value = strconv.FormatUint(uint64(defaultVal[0]), 10)
|
||||
return defaultVal[0]
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// MustUint64 always returns value without error,
|
||||
// it returns 0 if error occurs.
|
||||
func (k *Key) MustUint64(defaultVal ...uint64) uint64 {
|
||||
val, err := k.Uint64()
|
||||
if len(defaultVal) > 0 && err != nil {
|
||||
k.value = strconv.FormatUint(defaultVal[0], 10)
|
||||
return defaultVal[0]
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// MustDuration always returns value without error,
|
||||
// it returns zero value if error occurs.
|
||||
func (k *Key) MustDuration(defaultVal ...time.Duration) time.Duration {
|
||||
val, err := k.Duration()
|
||||
if len(defaultVal) > 0 && err != nil {
|
||||
k.value = defaultVal[0].String()
|
||||
return defaultVal[0]
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// MustTimeFormat always parses with given format and returns value without error,
|
||||
// it returns zero value if error occurs.
|
||||
func (k *Key) MustTimeFormat(format string, defaultVal ...time.Time) time.Time {
|
||||
val, err := k.TimeFormat(format)
|
||||
if len(defaultVal) > 0 && err != nil {
|
||||
k.value = defaultVal[0].Format(format)
|
||||
return defaultVal[0]
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// MustTime always parses with RFC3339 format and returns value without error,
|
||||
// it returns zero value if error occurs.
|
||||
func (k *Key) MustTime(defaultVal ...time.Time) time.Time {
|
||||
return k.MustTimeFormat(time.RFC3339, defaultVal...)
|
||||
}
|
||||
|
||||
// In always returns value without error,
|
||||
// it returns default value if error occurs or doesn't fit into candidates.
|
||||
func (k *Key) In(defaultVal string, candidates []string) string {
|
||||
val := k.String()
|
||||
for _, cand := range candidates {
|
||||
if val == cand {
|
||||
return val
|
||||
}
|
||||
}
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
// InFloat64 always returns value without error,
|
||||
// it returns default value if error occurs or doesn't fit into candidates.
|
||||
func (k *Key) InFloat64(defaultVal float64, candidates []float64) float64 {
|
||||
val := k.MustFloat64()
|
||||
for _, cand := range candidates {
|
||||
if val == cand {
|
||||
return val
|
||||
}
|
||||
}
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
// InInt always returns value without error,
|
||||
// it returns default value if error occurs or doesn't fit into candidates.
|
||||
func (k *Key) InInt(defaultVal int, candidates []int) int {
|
||||
val := k.MustInt()
|
||||
for _, cand := range candidates {
|
||||
if val == cand {
|
||||
return val
|
||||
}
|
||||
}
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
// InInt64 always returns value without error,
|
||||
// it returns default value if error occurs or doesn't fit into candidates.
|
||||
func (k *Key) InInt64(defaultVal int64, candidates []int64) int64 {
|
||||
val := k.MustInt64()
|
||||
for _, cand := range candidates {
|
||||
if val == cand {
|
||||
return val
|
||||
}
|
||||
}
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
// InUint always returns value without error,
|
||||
// it returns default value if error occurs or doesn't fit into candidates.
|
||||
func (k *Key) InUint(defaultVal uint, candidates []uint) uint {
|
||||
val := k.MustUint()
|
||||
for _, cand := range candidates {
|
||||
if val == cand {
|
||||
return val
|
||||
}
|
||||
}
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
// InUint64 always returns value without error,
|
||||
// it returns default value if error occurs or doesn't fit into candidates.
|
||||
func (k *Key) InUint64(defaultVal uint64, candidates []uint64) uint64 {
|
||||
val := k.MustUint64()
|
||||
for _, cand := range candidates {
|
||||
if val == cand {
|
||||
return val
|
||||
}
|
||||
}
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
// InTimeFormat always parses with given format and returns value without error,
|
||||
// it returns default value if error occurs or doesn't fit into candidates.
|
||||
func (k *Key) InTimeFormat(format string, defaultVal time.Time, candidates []time.Time) time.Time {
|
||||
val := k.MustTimeFormat(format)
|
||||
for _, cand := range candidates {
|
||||
if val == cand {
|
||||
return val
|
||||
}
|
||||
}
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
// InTime always parses with RFC3339 format and returns value without error,
|
||||
// it returns default value if error occurs or doesn't fit into candidates.
|
||||
func (k *Key) InTime(defaultVal time.Time, candidates []time.Time) time.Time {
|
||||
return k.InTimeFormat(time.RFC3339, defaultVal, candidates)
|
||||
}
|
||||
|
||||
// RangeFloat64 checks if value is in given range inclusively,
|
||||
// and returns default value if it's not.
|
||||
func (k *Key) RangeFloat64(defaultVal, min, max float64) float64 {
|
||||
val := k.MustFloat64()
|
||||
if val < min || val > max {
|
||||
return defaultVal
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// RangeInt checks if value is in given range inclusively,
|
||||
// and returns default value if it's not.
|
||||
func (k *Key) RangeInt(defaultVal, min, max int) int {
|
||||
val := k.MustInt()
|
||||
if val < min || val > max {
|
||||
return defaultVal
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// RangeInt64 checks if value is in given range inclusively,
|
||||
// and returns default value if it's not.
|
||||
func (k *Key) RangeInt64(defaultVal, min, max int64) int64 {
|
||||
val := k.MustInt64()
|
||||
if val < min || val > max {
|
||||
return defaultVal
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// RangeTimeFormat checks if value with given format is in given range inclusively,
|
||||
// and returns default value if it's not.
|
||||
func (k *Key) RangeTimeFormat(format string, defaultVal, min, max time.Time) time.Time {
|
||||
val := k.MustTimeFormat(format)
|
||||
if val.Unix() < min.Unix() || val.Unix() > max.Unix() {
|
||||
return defaultVal
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// RangeTime checks if value with RFC3339 format is in given range inclusively,
|
||||
// and returns default value if it's not.
|
||||
func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time {
|
||||
return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max)
|
||||
}
|
||||
|
||||
// Strings returns list of string divided by given delimiter.
|
||||
func (k *Key) Strings(delim string) []string {
|
||||
str := k.String()
|
||||
if len(str) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
runes := []rune(str)
|
||||
vals := make([]string, 0, 2)
|
||||
var buf bytes.Buffer
|
||||
escape := false
|
||||
idx := 0
|
||||
for {
|
||||
if escape {
|
||||
escape = false
|
||||
if runes[idx] != '\\' && !strings.HasPrefix(string(runes[idx:]), delim) {
|
||||
buf.WriteRune('\\')
|
||||
}
|
||||
buf.WriteRune(runes[idx])
|
||||
} else {
|
||||
if runes[idx] == '\\' {
|
||||
escape = true
|
||||
} else if strings.HasPrefix(string(runes[idx:]), delim) {
|
||||
idx += len(delim) - 1
|
||||
vals = append(vals, strings.TrimSpace(buf.String()))
|
||||
buf.Reset()
|
||||
} else {
|
||||
buf.WriteRune(runes[idx])
|
||||
}
|
||||
}
|
||||
idx++
|
||||
if idx == len(runes) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if buf.Len() > 0 {
|
||||
vals = append(vals, strings.TrimSpace(buf.String()))
|
||||
}
|
||||
|
||||
return vals
|
||||
}
|
||||
|
||||
// StringsWithShadows returns list of string divided by given delimiter.
|
||||
// Shadows will also be appended if any.
|
||||
func (k *Key) StringsWithShadows(delim string) []string {
|
||||
vals := k.ValueWithShadows()
|
||||
results := make([]string, 0, len(vals)*2)
|
||||
for i := range vals {
|
||||
if len(vals) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
results = append(results, strings.Split(vals[i], delim)...)
|
||||
}
|
||||
|
||||
for i := range results {
|
||||
results[i] = k.transformValue(strings.TrimSpace(results[i]))
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// Float64s returns list of float64 divided by given delimiter. Any invalid input will be treated as zero value.
|
||||
func (k *Key) Float64s(delim string) []float64 {
|
||||
vals, _ := k.parseFloat64s(k.Strings(delim), true, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
// Ints returns list of int divided by given delimiter. Any invalid input will be treated as zero value.
|
||||
func (k *Key) Ints(delim string) []int {
|
||||
vals, _ := k.parseInts(k.Strings(delim), true, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
// Int64s returns list of int64 divided by given delimiter. Any invalid input will be treated as zero value.
|
||||
func (k *Key) Int64s(delim string) []int64 {
|
||||
vals, _ := k.parseInt64s(k.Strings(delim), true, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
// Uints returns list of uint divided by given delimiter. Any invalid input will be treated as zero value.
|
||||
func (k *Key) Uints(delim string) []uint {
|
||||
vals, _ := k.parseUints(k.Strings(delim), true, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
// Uint64s returns list of uint64 divided by given delimiter. Any invalid input will be treated as zero value.
|
||||
func (k *Key) Uint64s(delim string) []uint64 {
|
||||
vals, _ := k.parseUint64s(k.Strings(delim), true, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
// Bools returns list of bool divided by given delimiter. Any invalid input will be treated as zero value.
|
||||
func (k *Key) Bools(delim string) []bool {
|
||||
vals, _ := k.parseBools(k.Strings(delim), true, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
// TimesFormat parses with given format and returns list of time.Time divided by given delimiter.
|
||||
// Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
|
||||
func (k *Key) TimesFormat(format, delim string) []time.Time {
|
||||
vals, _ := k.parseTimesFormat(format, k.Strings(delim), true, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
// Times parses with RFC3339 format and returns list of time.Time divided by given delimiter.
|
||||
// Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
|
||||
func (k *Key) Times(delim string) []time.Time {
|
||||
return k.TimesFormat(time.RFC3339, delim)
|
||||
}
|
||||
|
||||
// ValidFloat64s returns list of float64 divided by given delimiter. If some value is not float, then
|
||||
// it will not be included to result list.
|
||||
func (k *Key) ValidFloat64s(delim string) []float64 {
|
||||
vals, _ := k.parseFloat64s(k.Strings(delim), false, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
// ValidInts returns list of int divided by given delimiter. If some value is not integer, then it will
|
||||
// not be included to result list.
|
||||
func (k *Key) ValidInts(delim string) []int {
|
||||
vals, _ := k.parseInts(k.Strings(delim), false, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
// ValidInt64s returns list of int64 divided by given delimiter. If some value is not 64-bit integer,
|
||||
// then it will not be included to result list.
|
||||
func (k *Key) ValidInt64s(delim string) []int64 {
|
||||
vals, _ := k.parseInt64s(k.Strings(delim), false, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
// ValidUints returns list of uint divided by given delimiter. If some value is not unsigned integer,
|
||||
// then it will not be included to result list.
|
||||
func (k *Key) ValidUints(delim string) []uint {
|
||||
vals, _ := k.parseUints(k.Strings(delim), false, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
// ValidUint64s returns list of uint64 divided by given delimiter. If some value is not 64-bit unsigned
|
||||
// integer, then it will not be included to result list.
|
||||
func (k *Key) ValidUint64s(delim string) []uint64 {
|
||||
vals, _ := k.parseUint64s(k.Strings(delim), false, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
// ValidBools returns list of bool divided by given delimiter. If some value is not 64-bit unsigned
|
||||
// integer, then it will not be included to result list.
|
||||
func (k *Key) ValidBools(delim string) []bool {
|
||||
vals, _ := k.parseBools(k.Strings(delim), false, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
// ValidTimesFormat parses with given format and returns list of time.Time divided by given delimiter.
|
||||
func (k *Key) ValidTimesFormat(format, delim string) []time.Time {
|
||||
vals, _ := k.parseTimesFormat(format, k.Strings(delim), false, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
// ValidTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter.
|
||||
func (k *Key) ValidTimes(delim string) []time.Time {
|
||||
return k.ValidTimesFormat(time.RFC3339, delim)
|
||||
}
|
||||
|
||||
// StrictFloat64s returns list of float64 divided by given delimiter or error on first invalid input.
|
||||
func (k *Key) StrictFloat64s(delim string) ([]float64, error) {
|
||||
return k.parseFloat64s(k.Strings(delim), false, true)
|
||||
}
|
||||
|
||||
// StrictInts returns list of int divided by given delimiter or error on first invalid input.
|
||||
func (k *Key) StrictInts(delim string) ([]int, error) {
|
||||
return k.parseInts(k.Strings(delim), false, true)
|
||||
}
|
||||
|
||||
// StrictInt64s returns list of int64 divided by given delimiter or error on first invalid input.
|
||||
func (k *Key) StrictInt64s(delim string) ([]int64, error) {
|
||||
return k.parseInt64s(k.Strings(delim), false, true)
|
||||
}
|
||||
|
||||
// StrictUints returns list of uint divided by given delimiter or error on first invalid input.
|
||||
func (k *Key) StrictUints(delim string) ([]uint, error) {
|
||||
return k.parseUints(k.Strings(delim), false, true)
|
||||
}
|
||||
|
||||
// StrictUint64s returns list of uint64 divided by given delimiter or error on first invalid input.
|
||||
func (k *Key) StrictUint64s(delim string) ([]uint64, error) {
|
||||
return k.parseUint64s(k.Strings(delim), false, true)
|
||||
}
|
||||
|
||||
// StrictBools returns list of bool divided by given delimiter or error on first invalid input.
|
||||
func (k *Key) StrictBools(delim string) ([]bool, error) {
|
||||
return k.parseBools(k.Strings(delim), false, true)
|
||||
}
|
||||
|
||||
// StrictTimesFormat parses with given format and returns list of time.Time divided by given delimiter
|
||||
// or error on first invalid input.
|
||||
func (k *Key) StrictTimesFormat(format, delim string) ([]time.Time, error) {
|
||||
return k.parseTimesFormat(format, k.Strings(delim), false, true)
|
||||
}
|
||||
|
||||
// StrictTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter
|
||||
// or error on first invalid input.
|
||||
func (k *Key) StrictTimes(delim string) ([]time.Time, error) {
|
||||
return k.StrictTimesFormat(time.RFC3339, delim)
|
||||
}
|
||||
|
||||
// parseBools transforms strings to bools.
|
||||
func (k *Key) parseBools(strs []string, addInvalid, returnOnInvalid bool) ([]bool, error) {
|
||||
vals := make([]bool, 0, len(strs))
|
||||
parser := func(str string) (interface{}, error) {
|
||||
val, err := parseBool(str)
|
||||
return val, err
|
||||
}
|
||||
rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
|
||||
if err == nil {
|
||||
for _, val := range rawVals {
|
||||
vals = append(vals, val.(bool))
|
||||
}
|
||||
}
|
||||
return vals, err
|
||||
}
|
||||
|
||||
// parseFloat64s transforms strings to float64s.
|
||||
func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]float64, error) {
|
||||
vals := make([]float64, 0, len(strs))
|
||||
parser := func(str string) (interface{}, error) {
|
||||
val, err := strconv.ParseFloat(str, 64)
|
||||
return val, err
|
||||
}
|
||||
rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
|
||||
if err == nil {
|
||||
for _, val := range rawVals {
|
||||
vals = append(vals, val.(float64))
|
||||
}
|
||||
}
|
||||
return vals, err
|
||||
}
|
||||
|
||||
// parseInts transforms strings to ints.
|
||||
func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) {
|
||||
vals := make([]int, 0, len(strs))
|
||||
parser := func(str string) (interface{}, error) {
|
||||
val, err := strconv.ParseInt(str, 0, 64)
|
||||
return val, err
|
||||
}
|
||||
rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
|
||||
if err == nil {
|
||||
for _, val := range rawVals {
|
||||
vals = append(vals, int(val.(int64)))
|
||||
}
|
||||
}
|
||||
return vals, err
|
||||
}
|
||||
|
||||
// parseInt64s transforms strings to int64s.
|
||||
func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) {
|
||||
vals := make([]int64, 0, len(strs))
|
||||
parser := func(str string) (interface{}, error) {
|
||||
val, err := strconv.ParseInt(str, 0, 64)
|
||||
return val, err
|
||||
}
|
||||
|
||||
rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
|
||||
if err == nil {
|
||||
for _, val := range rawVals {
|
||||
vals = append(vals, val.(int64))
|
||||
}
|
||||
}
|
||||
return vals, err
|
||||
}
|
||||
|
||||
// parseUints transforms strings to uints.
|
||||
func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) {
|
||||
vals := make([]uint, 0, len(strs))
|
||||
parser := func(str string) (interface{}, error) {
|
||||
val, err := strconv.ParseUint(str, 0, 64)
|
||||
return val, err
|
||||
}
|
||||
|
||||
rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
|
||||
if err == nil {
|
||||
for _, val := range rawVals {
|
||||
vals = append(vals, uint(val.(uint64)))
|
||||
}
|
||||
}
|
||||
return vals, err
|
||||
}
|
||||
|
||||
// parseUint64s transforms strings to uint64s.
|
||||
func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) {
|
||||
vals := make([]uint64, 0, len(strs))
|
||||
parser := func(str string) (interface{}, error) {
|
||||
val, err := strconv.ParseUint(str, 0, 64)
|
||||
return val, err
|
||||
}
|
||||
rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
|
||||
if err == nil {
|
||||
for _, val := range rawVals {
|
||||
vals = append(vals, val.(uint64))
|
||||
}
|
||||
}
|
||||
return vals, err
|
||||
}
|
||||
|
||||
type Parser func(str string) (interface{}, error)
|
||||
|
||||
// parseTimesFormat transforms strings to times in given format.
|
||||
func (k *Key) parseTimesFormat(format string, strs []string, addInvalid, returnOnInvalid bool) ([]time.Time, error) {
|
||||
vals := make([]time.Time, 0, len(strs))
|
||||
parser := func(str string) (interface{}, error) {
|
||||
val, err := time.Parse(format, str)
|
||||
return val, err
|
||||
}
|
||||
rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
|
||||
if err == nil {
|
||||
for _, val := range rawVals {
|
||||
vals = append(vals, val.(time.Time))
|
||||
}
|
||||
}
|
||||
return vals, err
|
||||
}
|
||||
|
||||
// doParse transforms strings to different types
|
||||
func (k *Key) doParse(strs []string, addInvalid, returnOnInvalid bool, parser Parser) ([]interface{}, error) {
|
||||
vals := make([]interface{}, 0, len(strs))
|
||||
for _, str := range strs {
|
||||
val, err := parser(str)
|
||||
if err != nil && returnOnInvalid {
|
||||
return nil, err
|
||||
}
|
||||
if err == nil || addInvalid {
|
||||
vals = append(vals, val)
|
||||
}
|
||||
}
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
// SetValue changes key value.
|
||||
func (k *Key) SetValue(v string) {
|
||||
if k.s.f.BlockMode {
|
||||
k.s.f.lock.Lock()
|
||||
defer k.s.f.lock.Unlock()
|
||||
}
|
||||
|
||||
k.value = v
|
||||
k.s.keysHash[k.name] = v
|
||||
}
|
||||
-520
@@ -1,520 +0,0 @@
|
||||
// Copyright 2015 Unknwon
|
||||
//
|
||||
// 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 ini
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const minReaderBufferSize = 4096
|
||||
|
||||
var pythonMultiline = regexp.MustCompile(`^([\t\f ]+)(.*)`)
|
||||
|
||||
type parserOptions struct {
|
||||
IgnoreContinuation bool
|
||||
IgnoreInlineComment bool
|
||||
AllowPythonMultilineValues bool
|
||||
SpaceBeforeInlineComment bool
|
||||
UnescapeValueDoubleQuotes bool
|
||||
UnescapeValueCommentSymbols bool
|
||||
PreserveSurroundedQuote bool
|
||||
DebugFunc DebugFunc
|
||||
ReaderBufferSize int
|
||||
}
|
||||
|
||||
type parser struct {
|
||||
buf *bufio.Reader
|
||||
options parserOptions
|
||||
|
||||
isEOF bool
|
||||
count int
|
||||
comment *bytes.Buffer
|
||||
}
|
||||
|
||||
func (p *parser) debug(format string, args ...interface{}) {
|
||||
if p.options.DebugFunc != nil {
|
||||
p.options.DebugFunc(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func newParser(r io.Reader, opts parserOptions) *parser {
|
||||
size := opts.ReaderBufferSize
|
||||
if size < minReaderBufferSize {
|
||||
size = minReaderBufferSize
|
||||
}
|
||||
|
||||
return &parser{
|
||||
buf: bufio.NewReaderSize(r, size),
|
||||
options: opts,
|
||||
count: 1,
|
||||
comment: &bytes.Buffer{},
|
||||
}
|
||||
}
|
||||
|
||||
// BOM handles header of UTF-8, UTF-16 LE and UTF-16 BE's BOM format.
|
||||
// http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding
|
||||
func (p *parser) BOM() error {
|
||||
mask, err := p.buf.Peek(2)
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
} else if len(mask) < 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case mask[0] == 254 && mask[1] == 255:
|
||||
fallthrough
|
||||
case mask[0] == 255 && mask[1] == 254:
|
||||
_, err = p.buf.Read(mask)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case mask[0] == 239 && mask[1] == 187:
|
||||
mask, err := p.buf.Peek(3)
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
} else if len(mask) < 3 {
|
||||
return nil
|
||||
}
|
||||
if mask[2] == 191 {
|
||||
_, err = p.buf.Read(mask)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *parser) readUntil(delim byte) ([]byte, error) {
|
||||
data, err := p.buf.ReadBytes(delim)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
p.isEOF = true
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func cleanComment(in []byte) ([]byte, bool) {
|
||||
i := bytes.IndexAny(in, "#;")
|
||||
if i == -1 {
|
||||
return nil, false
|
||||
}
|
||||
return in[i:], true
|
||||
}
|
||||
|
||||
func readKeyName(delimiters string, in []byte) (string, int, error) {
|
||||
line := string(in)
|
||||
|
||||
// Check if key name surrounded by quotes.
|
||||
var keyQuote string
|
||||
if line[0] == '"' {
|
||||
if len(line) > 6 && line[0:3] == `"""` {
|
||||
keyQuote = `"""`
|
||||
} else {
|
||||
keyQuote = `"`
|
||||
}
|
||||
} else if line[0] == '`' {
|
||||
keyQuote = "`"
|
||||
}
|
||||
|
||||
// Get out key name
|
||||
var endIdx int
|
||||
if len(keyQuote) > 0 {
|
||||
startIdx := len(keyQuote)
|
||||
// FIXME: fail case -> """"""name"""=value
|
||||
pos := strings.Index(line[startIdx:], keyQuote)
|
||||
if pos == -1 {
|
||||
return "", -1, fmt.Errorf("missing closing key quote: %s", line)
|
||||
}
|
||||
pos += startIdx
|
||||
|
||||
// Find key-value delimiter
|
||||
i := strings.IndexAny(line[pos+startIdx:], delimiters)
|
||||
if i < 0 {
|
||||
return "", -1, ErrDelimiterNotFound{line}
|
||||
}
|
||||
endIdx = pos + i
|
||||
return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil
|
||||
}
|
||||
|
||||
endIdx = strings.IndexAny(line, delimiters)
|
||||
if endIdx < 0 {
|
||||
return "", -1, ErrDelimiterNotFound{line}
|
||||
}
|
||||
if endIdx == 0 {
|
||||
return "", -1, ErrEmptyKeyName{line}
|
||||
}
|
||||
|
||||
return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil
|
||||
}
|
||||
|
||||
func (p *parser) readMultilines(line, val, valQuote string) (string, error) {
|
||||
for {
|
||||
data, err := p.readUntil('\n')
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
next := string(data)
|
||||
|
||||
pos := strings.LastIndex(next, valQuote)
|
||||
if pos > -1 {
|
||||
val += next[:pos]
|
||||
|
||||
comment, has := cleanComment([]byte(next[pos:]))
|
||||
if has {
|
||||
p.comment.Write(bytes.TrimSpace(comment))
|
||||
}
|
||||
break
|
||||
}
|
||||
val += next
|
||||
if p.isEOF {
|
||||
return "", fmt.Errorf("missing closing key quote from %q to %q", line, next)
|
||||
}
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (p *parser) readContinuationLines(val string) (string, error) {
|
||||
for {
|
||||
data, err := p.readUntil('\n')
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
next := strings.TrimSpace(string(data))
|
||||
|
||||
if len(next) == 0 {
|
||||
break
|
||||
}
|
||||
val += next
|
||||
if val[len(val)-1] != '\\' {
|
||||
break
|
||||
}
|
||||
val = val[:len(val)-1]
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// hasSurroundedQuote check if and only if the first and last characters
|
||||
// are quotes \" or \'.
|
||||
// It returns false if any other parts also contain same kind of quotes.
|
||||
func hasSurroundedQuote(in string, quote byte) bool {
|
||||
return len(in) >= 2 && in[0] == quote && in[len(in)-1] == quote &&
|
||||
strings.IndexByte(in[1:], quote) == len(in)-2
|
||||
}
|
||||
|
||||
func (p *parser) readValue(in []byte, bufferSize int) (string, error) {
|
||||
|
||||
line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
|
||||
if len(line) == 0 {
|
||||
if p.options.AllowPythonMultilineValues && len(in) > 0 && in[len(in)-1] == '\n' {
|
||||
return p.readPythonMultilines(line, bufferSize)
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
var valQuote string
|
||||
if len(line) > 3 && line[0:3] == `"""` {
|
||||
valQuote = `"""`
|
||||
} else if line[0] == '`' {
|
||||
valQuote = "`"
|
||||
} else if p.options.UnescapeValueDoubleQuotes && line[0] == '"' {
|
||||
valQuote = `"`
|
||||
}
|
||||
|
||||
if len(valQuote) > 0 {
|
||||
startIdx := len(valQuote)
|
||||
pos := strings.LastIndex(line[startIdx:], valQuote)
|
||||
// Check for multi-line value
|
||||
if pos == -1 {
|
||||
return p.readMultilines(line, line[startIdx:], valQuote)
|
||||
}
|
||||
|
||||
if p.options.UnescapeValueDoubleQuotes && valQuote == `"` {
|
||||
return strings.Replace(line[startIdx:pos+startIdx], `\"`, `"`, -1), nil
|
||||
}
|
||||
return line[startIdx : pos+startIdx], nil
|
||||
}
|
||||
|
||||
lastChar := line[len(line)-1]
|
||||
// Won't be able to reach here if value only contains whitespace
|
||||
line = strings.TrimSpace(line)
|
||||
trimmedLastChar := line[len(line)-1]
|
||||
|
||||
// Check continuation lines when desired
|
||||
if !p.options.IgnoreContinuation && trimmedLastChar == '\\' {
|
||||
return p.readContinuationLines(line[:len(line)-1])
|
||||
}
|
||||
|
||||
// Check if ignore inline comment
|
||||
if !p.options.IgnoreInlineComment {
|
||||
var i int
|
||||
if p.options.SpaceBeforeInlineComment {
|
||||
i = strings.Index(line, " #")
|
||||
if i == -1 {
|
||||
i = strings.Index(line, " ;")
|
||||
}
|
||||
|
||||
} else {
|
||||
i = strings.IndexAny(line, "#;")
|
||||
}
|
||||
|
||||
if i > -1 {
|
||||
p.comment.WriteString(line[i:])
|
||||
line = strings.TrimSpace(line[:i])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Trim single and double quotes
|
||||
if (hasSurroundedQuote(line, '\'') ||
|
||||
hasSurroundedQuote(line, '"')) && !p.options.PreserveSurroundedQuote {
|
||||
line = line[1 : len(line)-1]
|
||||
} else if len(valQuote) == 0 && p.options.UnescapeValueCommentSymbols {
|
||||
line = strings.ReplaceAll(line, `\;`, ";")
|
||||
line = strings.ReplaceAll(line, `\#`, "#")
|
||||
} else if p.options.AllowPythonMultilineValues && lastChar == '\n' {
|
||||
return p.readPythonMultilines(line, bufferSize)
|
||||
}
|
||||
|
||||
return line, nil
|
||||
}
|
||||
|
||||
func (p *parser) readPythonMultilines(line string, bufferSize int) (string, error) {
|
||||
parserBufferPeekResult, _ := p.buf.Peek(bufferSize)
|
||||
peekBuffer := bytes.NewBuffer(parserBufferPeekResult)
|
||||
|
||||
for {
|
||||
peekData, peekErr := peekBuffer.ReadBytes('\n')
|
||||
if peekErr != nil && peekErr != io.EOF {
|
||||
p.debug("readPythonMultilines: failed to peek with error: %v", peekErr)
|
||||
return "", peekErr
|
||||
}
|
||||
|
||||
p.debug("readPythonMultilines: parsing %q", string(peekData))
|
||||
|
||||
peekMatches := pythonMultiline.FindStringSubmatch(string(peekData))
|
||||
p.debug("readPythonMultilines: matched %d parts", len(peekMatches))
|
||||
for n, v := range peekMatches {
|
||||
p.debug(" %d: %q", n, v)
|
||||
}
|
||||
|
||||
// Return if not a Python multiline value.
|
||||
if len(peekMatches) != 3 {
|
||||
p.debug("readPythonMultilines: end of value, got: %q", line)
|
||||
return line, nil
|
||||
}
|
||||
|
||||
// Advance the parser reader (buffer) in-sync with the peek buffer.
|
||||
_, err := p.buf.Discard(len(peekData))
|
||||
if err != nil {
|
||||
p.debug("readPythonMultilines: failed to skip to the end, returning error")
|
||||
return "", err
|
||||
}
|
||||
|
||||
line += "\n" + peekMatches[0]
|
||||
}
|
||||
}
|
||||
|
||||
// parse parses data through an io.Reader.
|
||||
func (f *File) parse(reader io.Reader) (err error) {
|
||||
p := newParser(reader, parserOptions{
|
||||
IgnoreContinuation: f.options.IgnoreContinuation,
|
||||
IgnoreInlineComment: f.options.IgnoreInlineComment,
|
||||
AllowPythonMultilineValues: f.options.AllowPythonMultilineValues,
|
||||
SpaceBeforeInlineComment: f.options.SpaceBeforeInlineComment,
|
||||
UnescapeValueDoubleQuotes: f.options.UnescapeValueDoubleQuotes,
|
||||
UnescapeValueCommentSymbols: f.options.UnescapeValueCommentSymbols,
|
||||
PreserveSurroundedQuote: f.options.PreserveSurroundedQuote,
|
||||
DebugFunc: f.options.DebugFunc,
|
||||
ReaderBufferSize: f.options.ReaderBufferSize,
|
||||
})
|
||||
if err = p.BOM(); err != nil {
|
||||
return fmt.Errorf("BOM: %v", err)
|
||||
}
|
||||
|
||||
// Ignore error because default section name is never empty string.
|
||||
name := DefaultSection
|
||||
if f.options.Insensitive || f.options.InsensitiveSections {
|
||||
name = strings.ToLower(DefaultSection)
|
||||
}
|
||||
section, _ := f.NewSection(name)
|
||||
|
||||
// This "last" is not strictly equivalent to "previous one" if current key is not the first nested key
|
||||
var isLastValueEmpty bool
|
||||
var lastRegularKey *Key
|
||||
|
||||
var line []byte
|
||||
var inUnparseableSection bool
|
||||
|
||||
// NOTE: Iterate and increase `currentPeekSize` until
|
||||
// the size of the parser buffer is found.
|
||||
// TODO(unknwon): When Golang 1.10 is the lowest version supported, replace with `parserBufferSize := p.buf.Size()`.
|
||||
parserBufferSize := 0
|
||||
// NOTE: Peek 4kb at a time.
|
||||
currentPeekSize := minReaderBufferSize
|
||||
|
||||
if f.options.AllowPythonMultilineValues {
|
||||
for {
|
||||
peekBytes, _ := p.buf.Peek(currentPeekSize)
|
||||
peekBytesLength := len(peekBytes)
|
||||
|
||||
if parserBufferSize >= peekBytesLength {
|
||||
break
|
||||
}
|
||||
|
||||
currentPeekSize *= 2
|
||||
parserBufferSize = peekBytesLength
|
||||
}
|
||||
}
|
||||
|
||||
for !p.isEOF {
|
||||
line, err = p.readUntil('\n')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f.options.AllowNestedValues &&
|
||||
isLastValueEmpty && len(line) > 0 {
|
||||
if line[0] == ' ' || line[0] == '\t' {
|
||||
err = lastRegularKey.addNestedValue(string(bytes.TrimSpace(line)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
line = bytes.TrimLeftFunc(line, unicode.IsSpace)
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Comments
|
||||
if line[0] == '#' || line[0] == ';' {
|
||||
// Note: we do not care ending line break,
|
||||
// it is needed for adding second line,
|
||||
// so just clean it once at the end when set to value.
|
||||
p.comment.Write(line)
|
||||
continue
|
||||
}
|
||||
|
||||
// Section
|
||||
if line[0] == '[' {
|
||||
// Read to the next ']' (TODO: support quoted strings)
|
||||
closeIdx := bytes.LastIndexByte(line, ']')
|
||||
if closeIdx == -1 {
|
||||
return fmt.Errorf("unclosed section: %s", line)
|
||||
}
|
||||
|
||||
name := string(line[1:closeIdx])
|
||||
section, err = f.NewSection(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
comment, has := cleanComment(line[closeIdx+1:])
|
||||
if has {
|
||||
p.comment.Write(comment)
|
||||
}
|
||||
|
||||
section.Comment = strings.TrimSpace(p.comment.String())
|
||||
|
||||
// Reset auto-counter and comments
|
||||
p.comment.Reset()
|
||||
p.count = 1
|
||||
// Nested values can't span sections
|
||||
isLastValueEmpty = false
|
||||
|
||||
inUnparseableSection = false
|
||||
for i := range f.options.UnparseableSections {
|
||||
if f.options.UnparseableSections[i] == name ||
|
||||
((f.options.Insensitive || f.options.InsensitiveSections) && strings.EqualFold(f.options.UnparseableSections[i], name)) {
|
||||
inUnparseableSection = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if inUnparseableSection {
|
||||
section.isRawSection = true
|
||||
section.rawBody += string(line)
|
||||
continue
|
||||
}
|
||||
|
||||
kname, offset, err := readKeyName(f.options.KeyValueDelimiters, line)
|
||||
if err != nil {
|
||||
switch {
|
||||
// Treat as boolean key when desired, and whole line is key name.
|
||||
case IsErrDelimiterNotFound(err):
|
||||
switch {
|
||||
case f.options.AllowBooleanKeys:
|
||||
kname, err := p.readValue(line, parserBufferSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key, err := section.NewBooleanKey(kname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key.Comment = strings.TrimSpace(p.comment.String())
|
||||
p.comment.Reset()
|
||||
continue
|
||||
|
||||
case f.options.SkipUnrecognizableLines:
|
||||
continue
|
||||
}
|
||||
case IsErrEmptyKeyName(err) && f.options.SkipUnrecognizableLines:
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Auto increment.
|
||||
isAutoIncr := false
|
||||
if kname == "-" {
|
||||
isAutoIncr = true
|
||||
kname = "#" + strconv.Itoa(p.count)
|
||||
p.count++
|
||||
}
|
||||
|
||||
value, err := p.readValue(line[offset:], parserBufferSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isLastValueEmpty = len(value) == 0
|
||||
|
||||
key, err := section.NewKey(kname, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key.isAutoIncrement = isAutoIncr
|
||||
key.Comment = strings.TrimSpace(p.comment.String())
|
||||
p.comment.Reset()
|
||||
lastRegularKey = key
|
||||
}
|
||||
return nil
|
||||
}
|
||||
-256
@@ -1,256 +0,0 @@
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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 ini
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Section represents a config section.
|
||||
type Section struct {
|
||||
f *File
|
||||
Comment string
|
||||
name string
|
||||
keys map[string]*Key
|
||||
keyList []string
|
||||
keysHash map[string]string
|
||||
|
||||
isRawSection bool
|
||||
rawBody string
|
||||
}
|
||||
|
||||
func newSection(f *File, name string) *Section {
|
||||
return &Section{
|
||||
f: f,
|
||||
name: name,
|
||||
keys: make(map[string]*Key),
|
||||
keyList: make([]string, 0, 10),
|
||||
keysHash: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns name of Section.
|
||||
func (s *Section) Name() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
// Body returns rawBody of Section if the section was marked as unparseable.
|
||||
// It still follows the other rules of the INI format surrounding leading/trailing whitespace.
|
||||
func (s *Section) Body() string {
|
||||
return strings.TrimSpace(s.rawBody)
|
||||
}
|
||||
|
||||
// SetBody updates body content only if section is raw.
|
||||
func (s *Section) SetBody(body string) {
|
||||
if !s.isRawSection {
|
||||
return
|
||||
}
|
||||
s.rawBody = body
|
||||
}
|
||||
|
||||
// NewKey creates a new key to given section.
|
||||
func (s *Section) NewKey(name, val string) (*Key, error) {
|
||||
if len(name) == 0 {
|
||||
return nil, errors.New("error creating new key: empty key name")
|
||||
} else if s.f.options.Insensitive || s.f.options.InsensitiveKeys {
|
||||
name = strings.ToLower(name)
|
||||
}
|
||||
|
||||
if s.f.BlockMode {
|
||||
s.f.lock.Lock()
|
||||
defer s.f.lock.Unlock()
|
||||
}
|
||||
|
||||
if inSlice(name, s.keyList) {
|
||||
if s.f.options.AllowShadows {
|
||||
if err := s.keys[name].addShadow(val); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
s.keys[name].value = val
|
||||
s.keysHash[name] = val
|
||||
}
|
||||
return s.keys[name], nil
|
||||
}
|
||||
|
||||
s.keyList = append(s.keyList, name)
|
||||
s.keys[name] = newKey(s, name, val)
|
||||
s.keysHash[name] = val
|
||||
return s.keys[name], nil
|
||||
}
|
||||
|
||||
// NewBooleanKey creates a new boolean type key to given section.
|
||||
func (s *Section) NewBooleanKey(name string) (*Key, error) {
|
||||
key, err := s.NewKey(name, "true")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key.isBooleanType = true
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// GetKey returns key in section by given name.
|
||||
func (s *Section) GetKey(name string) (*Key, error) {
|
||||
if s.f.BlockMode {
|
||||
s.f.lock.RLock()
|
||||
}
|
||||
if s.f.options.Insensitive || s.f.options.InsensitiveKeys {
|
||||
name = strings.ToLower(name)
|
||||
}
|
||||
key := s.keys[name]
|
||||
if s.f.BlockMode {
|
||||
s.f.lock.RUnlock()
|
||||
}
|
||||
|
||||
if key == nil {
|
||||
// Check if it is a child-section.
|
||||
sname := s.name
|
||||
for {
|
||||
if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 {
|
||||
sname = sname[:i]
|
||||
sec, err := s.f.GetSection(sname)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
return sec.GetKey(name)
|
||||
}
|
||||
break
|
||||
}
|
||||
return nil, fmt.Errorf("error when getting key of section %q: key %q not exists", s.name, name)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// HasKey returns true if section contains a key with given name.
|
||||
func (s *Section) HasKey(name string) bool {
|
||||
key, _ := s.GetKey(name)
|
||||
return key != nil
|
||||
}
|
||||
|
||||
// Deprecated: Use "HasKey" instead.
|
||||
func (s *Section) Haskey(name string) bool {
|
||||
return s.HasKey(name)
|
||||
}
|
||||
|
||||
// HasValue returns true if section contains given raw value.
|
||||
func (s *Section) HasValue(value string) bool {
|
||||
if s.f.BlockMode {
|
||||
s.f.lock.RLock()
|
||||
defer s.f.lock.RUnlock()
|
||||
}
|
||||
|
||||
for _, k := range s.keys {
|
||||
if value == k.value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Key assumes named Key exists in section and returns a zero-value when not.
|
||||
func (s *Section) Key(name string) *Key {
|
||||
key, err := s.GetKey(name)
|
||||
if err != nil {
|
||||
// It's OK here because the only possible error is empty key name,
|
||||
// but if it's empty, this piece of code won't be executed.
|
||||
key, _ = s.NewKey(name, "")
|
||||
return key
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
// Keys returns list of keys of section.
|
||||
func (s *Section) Keys() []*Key {
|
||||
keys := make([]*Key, len(s.keyList))
|
||||
for i := range s.keyList {
|
||||
keys[i] = s.Key(s.keyList[i])
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// ParentKeys returns list of keys of parent section.
|
||||
func (s *Section) ParentKeys() []*Key {
|
||||
var parentKeys []*Key
|
||||
sname := s.name
|
||||
for {
|
||||
if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 {
|
||||
sname = sname[:i]
|
||||
sec, err := s.f.GetSection(sname)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
parentKeys = append(parentKeys, sec.Keys()...)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
return parentKeys
|
||||
}
|
||||
|
||||
// KeyStrings returns list of key names of section.
|
||||
func (s *Section) KeyStrings() []string {
|
||||
list := make([]string, len(s.keyList))
|
||||
copy(list, s.keyList)
|
||||
return list
|
||||
}
|
||||
|
||||
// KeysHash returns keys hash consisting of names and values.
|
||||
func (s *Section) KeysHash() map[string]string {
|
||||
if s.f.BlockMode {
|
||||
s.f.lock.RLock()
|
||||
defer s.f.lock.RUnlock()
|
||||
}
|
||||
|
||||
hash := make(map[string]string, len(s.keysHash))
|
||||
for key, value := range s.keysHash {
|
||||
hash[key] = value
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
// DeleteKey deletes a key from section.
|
||||
func (s *Section) DeleteKey(name string) {
|
||||
if s.f.BlockMode {
|
||||
s.f.lock.Lock()
|
||||
defer s.f.lock.Unlock()
|
||||
}
|
||||
|
||||
for i, k := range s.keyList {
|
||||
if k == name {
|
||||
s.keyList = append(s.keyList[:i], s.keyList[i+1:]...)
|
||||
delete(s.keys, name)
|
||||
delete(s.keysHash, name)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ChildSections returns a list of child sections of current section.
|
||||
// For example, "[parent.child1]" and "[parent.child12]" are child sections
|
||||
// of section "[parent]".
|
||||
func (s *Section) ChildSections() []*Section {
|
||||
prefix := s.name + s.f.options.ChildSectionDelimiter
|
||||
children := make([]*Section, 0, 3)
|
||||
for _, name := range s.f.sectionList {
|
||||
if strings.HasPrefix(name, prefix) {
|
||||
children = append(children, s.f.sections[name]...)
|
||||
}
|
||||
}
|
||||
return children
|
||||
}
|
||||
-747
@@ -1,747 +0,0 @@
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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 ini
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// NameMapper represents a ini tag name mapper.
|
||||
type NameMapper func(string) string
|
||||
|
||||
// Built-in name getters.
|
||||
var (
|
||||
// SnackCase converts to format SNACK_CASE.
|
||||
SnackCase NameMapper = func(raw string) string {
|
||||
newstr := make([]rune, 0, len(raw))
|
||||
for i, chr := range raw {
|
||||
if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
|
||||
if i > 0 {
|
||||
newstr = append(newstr, '_')
|
||||
}
|
||||
}
|
||||
newstr = append(newstr, unicode.ToUpper(chr))
|
||||
}
|
||||
return string(newstr)
|
||||
}
|
||||
// TitleUnderscore converts to format title_underscore.
|
||||
TitleUnderscore NameMapper = func(raw string) string {
|
||||
newstr := make([]rune, 0, len(raw))
|
||||
for i, chr := range raw {
|
||||
if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
|
||||
if i > 0 {
|
||||
newstr = append(newstr, '_')
|
||||
}
|
||||
chr -= 'A' - 'a'
|
||||
}
|
||||
newstr = append(newstr, chr)
|
||||
}
|
||||
return string(newstr)
|
||||
}
|
||||
)
|
||||
|
||||
func (s *Section) parseFieldName(raw, actual string) string {
|
||||
if len(actual) > 0 {
|
||||
return actual
|
||||
}
|
||||
if s.f.NameMapper != nil {
|
||||
return s.f.NameMapper(raw)
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
func parseDelim(actual string) string {
|
||||
if len(actual) > 0 {
|
||||
return actual
|
||||
}
|
||||
return ","
|
||||
}
|
||||
|
||||
var reflectTime = reflect.TypeOf(time.Now()).Kind()
|
||||
|
||||
// setSliceWithProperType sets proper values to slice based on its type.
|
||||
func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
|
||||
var strs []string
|
||||
if allowShadow {
|
||||
strs = key.StringsWithShadows(delim)
|
||||
} else {
|
||||
strs = key.Strings(delim)
|
||||
}
|
||||
|
||||
numVals := len(strs)
|
||||
if numVals == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var vals interface{}
|
||||
var err error
|
||||
|
||||
sliceOf := field.Type().Elem().Kind()
|
||||
switch sliceOf {
|
||||
case reflect.String:
|
||||
vals = strs
|
||||
case reflect.Int:
|
||||
vals, err = key.parseInts(strs, true, false)
|
||||
case reflect.Int64:
|
||||
vals, err = key.parseInt64s(strs, true, false)
|
||||
case reflect.Uint:
|
||||
vals, err = key.parseUints(strs, true, false)
|
||||
case reflect.Uint64:
|
||||
vals, err = key.parseUint64s(strs, true, false)
|
||||
case reflect.Float64:
|
||||
vals, err = key.parseFloat64s(strs, true, false)
|
||||
case reflect.Bool:
|
||||
vals, err = key.parseBools(strs, true, false)
|
||||
case reflectTime:
|
||||
vals, err = key.parseTimesFormat(time.RFC3339, strs, true, false)
|
||||
default:
|
||||
return fmt.Errorf("unsupported type '[]%s'", sliceOf)
|
||||
}
|
||||
if err != nil && isStrict {
|
||||
return err
|
||||
}
|
||||
|
||||
slice := reflect.MakeSlice(field.Type(), numVals, numVals)
|
||||
for i := 0; i < numVals; i++ {
|
||||
switch sliceOf {
|
||||
case reflect.String:
|
||||
slice.Index(i).Set(reflect.ValueOf(vals.([]string)[i]))
|
||||
case reflect.Int:
|
||||
slice.Index(i).Set(reflect.ValueOf(vals.([]int)[i]))
|
||||
case reflect.Int64:
|
||||
slice.Index(i).Set(reflect.ValueOf(vals.([]int64)[i]))
|
||||
case reflect.Uint:
|
||||
slice.Index(i).Set(reflect.ValueOf(vals.([]uint)[i]))
|
||||
case reflect.Uint64:
|
||||
slice.Index(i).Set(reflect.ValueOf(vals.([]uint64)[i]))
|
||||
case reflect.Float64:
|
||||
slice.Index(i).Set(reflect.ValueOf(vals.([]float64)[i]))
|
||||
case reflect.Bool:
|
||||
slice.Index(i).Set(reflect.ValueOf(vals.([]bool)[i]))
|
||||
case reflectTime:
|
||||
slice.Index(i).Set(reflect.ValueOf(vals.([]time.Time)[i]))
|
||||
}
|
||||
}
|
||||
field.Set(slice)
|
||||
return nil
|
||||
}
|
||||
|
||||
func wrapStrictError(err error, isStrict bool) error {
|
||||
if isStrict {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// setWithProperType sets proper value to field based on its type,
|
||||
// but it does not return error for failing parsing,
|
||||
// because we want to use default value that is already assigned to struct.
|
||||
func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
|
||||
vt := t
|
||||
isPtr := t.Kind() == reflect.Ptr
|
||||
if isPtr {
|
||||
vt = t.Elem()
|
||||
}
|
||||
switch vt.Kind() {
|
||||
case reflect.String:
|
||||
stringVal := key.String()
|
||||
if isPtr {
|
||||
field.Set(reflect.ValueOf(&stringVal))
|
||||
} else if len(stringVal) > 0 {
|
||||
field.SetString(key.String())
|
||||
}
|
||||
case reflect.Bool:
|
||||
boolVal, err := key.Bool()
|
||||
if err != nil {
|
||||
return wrapStrictError(err, isStrict)
|
||||
}
|
||||
if isPtr {
|
||||
field.Set(reflect.ValueOf(&boolVal))
|
||||
} else {
|
||||
field.SetBool(boolVal)
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
// ParseDuration will not return err for `0`, so check the type name
|
||||
if vt.Name() == "Duration" {
|
||||
durationVal, err := key.Duration()
|
||||
if err != nil {
|
||||
if intVal, err := key.Int64(); err == nil {
|
||||
field.SetInt(intVal)
|
||||
return nil
|
||||
}
|
||||
return wrapStrictError(err, isStrict)
|
||||
}
|
||||
if isPtr {
|
||||
field.Set(reflect.ValueOf(&durationVal))
|
||||
} else if int64(durationVal) > 0 {
|
||||
field.Set(reflect.ValueOf(durationVal))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
intVal, err := key.Int64()
|
||||
if err != nil {
|
||||
return wrapStrictError(err, isStrict)
|
||||
}
|
||||
if isPtr {
|
||||
pv := reflect.New(t.Elem())
|
||||
pv.Elem().SetInt(intVal)
|
||||
field.Set(pv)
|
||||
} else {
|
||||
field.SetInt(intVal)
|
||||
}
|
||||
// byte is an alias for uint8, so supporting uint8 breaks support for byte
|
||||
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
durationVal, err := key.Duration()
|
||||
// Skip zero value
|
||||
if err == nil && uint64(durationVal) > 0 {
|
||||
if isPtr {
|
||||
field.Set(reflect.ValueOf(&durationVal))
|
||||
} else {
|
||||
field.Set(reflect.ValueOf(durationVal))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
uintVal, err := key.Uint64()
|
||||
if err != nil {
|
||||
return wrapStrictError(err, isStrict)
|
||||
}
|
||||
if isPtr {
|
||||
pv := reflect.New(t.Elem())
|
||||
pv.Elem().SetUint(uintVal)
|
||||
field.Set(pv)
|
||||
} else {
|
||||
field.SetUint(uintVal)
|
||||
}
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
floatVal, err := key.Float64()
|
||||
if err != nil {
|
||||
return wrapStrictError(err, isStrict)
|
||||
}
|
||||
if isPtr {
|
||||
pv := reflect.New(t.Elem())
|
||||
pv.Elem().SetFloat(floatVal)
|
||||
field.Set(pv)
|
||||
} else {
|
||||
field.SetFloat(floatVal)
|
||||
}
|
||||
case reflectTime:
|
||||
timeVal, err := key.Time()
|
||||
if err != nil {
|
||||
return wrapStrictError(err, isStrict)
|
||||
}
|
||||
if isPtr {
|
||||
field.Set(reflect.ValueOf(&timeVal))
|
||||
} else {
|
||||
field.Set(reflect.ValueOf(timeVal))
|
||||
}
|
||||
case reflect.Slice:
|
||||
return setSliceWithProperType(key, field, delim, allowShadow, isStrict)
|
||||
default:
|
||||
return fmt.Errorf("unsupported type %q", t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool, allowNonUnique bool, extends bool) {
|
||||
opts := strings.SplitN(tag, ",", 5)
|
||||
rawName = opts[0]
|
||||
for _, opt := range opts[1:] {
|
||||
omitEmpty = omitEmpty || (opt == "omitempty")
|
||||
allowShadow = allowShadow || (opt == "allowshadow")
|
||||
allowNonUnique = allowNonUnique || (opt == "nonunique")
|
||||
extends = extends || (opt == "extends")
|
||||
}
|
||||
return rawName, omitEmpty, allowShadow, allowNonUnique, extends
|
||||
}
|
||||
|
||||
// mapToField maps the given value to the matching field of the given section.
|
||||
// The sectionIndex is the index (if non unique sections are enabled) to which the value should be added.
|
||||
func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int, sectionName string) error {
|
||||
if val.Kind() == reflect.Ptr {
|
||||
val = val.Elem()
|
||||
}
|
||||
typ := val.Type()
|
||||
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
field := val.Field(i)
|
||||
tpField := typ.Field(i)
|
||||
|
||||
tag := tpField.Tag.Get("ini")
|
||||
if tag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
rawName, _, allowShadow, allowNonUnique, extends := parseTagOptions(tag)
|
||||
fieldName := s.parseFieldName(tpField.Name, rawName)
|
||||
if len(fieldName) == 0 || !field.CanSet() {
|
||||
continue
|
||||
}
|
||||
|
||||
isStruct := tpField.Type.Kind() == reflect.Struct
|
||||
isStructPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct
|
||||
isAnonymousPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous
|
||||
if isAnonymousPtr {
|
||||
field.Set(reflect.New(tpField.Type.Elem()))
|
||||
}
|
||||
|
||||
if extends && (isAnonymousPtr || (isStruct && tpField.Anonymous)) {
|
||||
if isStructPtr && field.IsNil() {
|
||||
field.Set(reflect.New(tpField.Type.Elem()))
|
||||
}
|
||||
fieldSection := s
|
||||
if rawName != "" {
|
||||
sectionName = s.name + s.f.options.ChildSectionDelimiter + rawName
|
||||
if secs, err := s.f.SectionsByName(sectionName); err == nil && sectionIndex < len(secs) {
|
||||
fieldSection = secs[sectionIndex]
|
||||
}
|
||||
}
|
||||
if err := fieldSection.mapToField(field, isStrict, sectionIndex, sectionName); err != nil {
|
||||
return fmt.Errorf("map to field %q: %v", fieldName, err)
|
||||
}
|
||||
} else if isAnonymousPtr || isStruct || isStructPtr {
|
||||
if secs, err := s.f.SectionsByName(fieldName); err == nil {
|
||||
if len(secs) <= sectionIndex {
|
||||
return fmt.Errorf("there are not enough sections (%d <= %d) for the field %q", len(secs), sectionIndex, fieldName)
|
||||
}
|
||||
// Only set the field to non-nil struct value if we have a section for it.
|
||||
// Otherwise, we end up with a non-nil struct ptr even though there is no data.
|
||||
if isStructPtr && field.IsNil() {
|
||||
field.Set(reflect.New(tpField.Type.Elem()))
|
||||
}
|
||||
if err = secs[sectionIndex].mapToField(field, isStrict, sectionIndex, fieldName); err != nil {
|
||||
return fmt.Errorf("map to field %q: %v", fieldName, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Map non-unique sections
|
||||
if allowNonUnique && tpField.Type.Kind() == reflect.Slice {
|
||||
newField, err := s.mapToSlice(fieldName, field, isStrict)
|
||||
if err != nil {
|
||||
return fmt.Errorf("map to slice %q: %v", fieldName, err)
|
||||
}
|
||||
|
||||
field.Set(newField)
|
||||
continue
|
||||
}
|
||||
|
||||
if key, err := s.GetKey(fieldName); err == nil {
|
||||
delim := parseDelim(tpField.Tag.Get("delim"))
|
||||
if err = setWithProperType(tpField.Type, key, field, delim, allowShadow, isStrict); err != nil {
|
||||
return fmt.Errorf("set field %q: %v", fieldName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mapToSlice maps all sections with the same name and returns the new value.
|
||||
// The type of the Value must be a slice.
|
||||
func (s *Section) mapToSlice(secName string, val reflect.Value, isStrict bool) (reflect.Value, error) {
|
||||
secs, err := s.f.SectionsByName(secName)
|
||||
if err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
|
||||
typ := val.Type().Elem()
|
||||
for i, sec := range secs {
|
||||
elem := reflect.New(typ)
|
||||
if err = sec.mapToField(elem, isStrict, i, sec.name); err != nil {
|
||||
return reflect.Value{}, fmt.Errorf("map to field from section %q: %v", secName, err)
|
||||
}
|
||||
|
||||
val = reflect.Append(val, elem.Elem())
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// mapTo maps a section to object v.
|
||||
func (s *Section) mapTo(v interface{}, isStrict bool) error {
|
||||
typ := reflect.TypeOf(v)
|
||||
val := reflect.ValueOf(v)
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
val = val.Elem()
|
||||
} else {
|
||||
return errors.New("not a pointer to a struct")
|
||||
}
|
||||
|
||||
if typ.Kind() == reflect.Slice {
|
||||
newField, err := s.mapToSlice(s.name, val, isStrict)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val.Set(newField)
|
||||
return nil
|
||||
}
|
||||
|
||||
return s.mapToField(val, isStrict, 0, s.name)
|
||||
}
|
||||
|
||||
// MapTo maps section to given struct.
|
||||
func (s *Section) MapTo(v interface{}) error {
|
||||
return s.mapTo(v, false)
|
||||
}
|
||||
|
||||
// StrictMapTo maps section to given struct in strict mode,
|
||||
// which returns all possible error including value parsing error.
|
||||
func (s *Section) StrictMapTo(v interface{}) error {
|
||||
return s.mapTo(v, true)
|
||||
}
|
||||
|
||||
// MapTo maps file to given struct.
|
||||
func (f *File) MapTo(v interface{}) error {
|
||||
return f.Section("").MapTo(v)
|
||||
}
|
||||
|
||||
// StrictMapTo maps file to given struct in strict mode,
|
||||
// which returns all possible error including value parsing error.
|
||||
func (f *File) StrictMapTo(v interface{}) error {
|
||||
return f.Section("").StrictMapTo(v)
|
||||
}
|
||||
|
||||
// MapToWithMapper maps data sources to given struct with name mapper.
|
||||
func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
|
||||
cfg, err := Load(source, others...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg.NameMapper = mapper
|
||||
return cfg.MapTo(v)
|
||||
}
|
||||
|
||||
// StrictMapToWithMapper maps data sources to given struct with name mapper in strict mode,
|
||||
// which returns all possible error including value parsing error.
|
||||
func StrictMapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
|
||||
cfg, err := Load(source, others...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg.NameMapper = mapper
|
||||
return cfg.StrictMapTo(v)
|
||||
}
|
||||
|
||||
// MapTo maps data sources to given struct.
|
||||
func MapTo(v, source interface{}, others ...interface{}) error {
|
||||
return MapToWithMapper(v, nil, source, others...)
|
||||
}
|
||||
|
||||
// StrictMapTo maps data sources to given struct in strict mode,
|
||||
// which returns all possible error including value parsing error.
|
||||
func StrictMapTo(v, source interface{}, others ...interface{}) error {
|
||||
return StrictMapToWithMapper(v, nil, source, others...)
|
||||
}
|
||||
|
||||
// reflectSliceWithProperType does the opposite thing as setSliceWithProperType.
|
||||
func reflectSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow bool) error {
|
||||
slice := field.Slice(0, field.Len())
|
||||
if field.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
sliceOf := field.Type().Elem().Kind()
|
||||
|
||||
if allowShadow {
|
||||
var keyWithShadows *Key
|
||||
for i := 0; i < field.Len(); i++ {
|
||||
var val string
|
||||
switch sliceOf {
|
||||
case reflect.String:
|
||||
val = slice.Index(i).String()
|
||||
case reflect.Int, reflect.Int64:
|
||||
val = fmt.Sprint(slice.Index(i).Int())
|
||||
case reflect.Uint, reflect.Uint64:
|
||||
val = fmt.Sprint(slice.Index(i).Uint())
|
||||
case reflect.Float64:
|
||||
val = fmt.Sprint(slice.Index(i).Float())
|
||||
case reflect.Bool:
|
||||
val = fmt.Sprint(slice.Index(i).Bool())
|
||||
case reflectTime:
|
||||
val = slice.Index(i).Interface().(time.Time).Format(time.RFC3339)
|
||||
default:
|
||||
return fmt.Errorf("unsupported type '[]%s'", sliceOf)
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
keyWithShadows = newKey(key.s, key.name, val)
|
||||
} else {
|
||||
_ = keyWithShadows.AddShadow(val)
|
||||
}
|
||||
}
|
||||
*key = *keyWithShadows
|
||||
return nil
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
for i := 0; i < field.Len(); i++ {
|
||||
switch sliceOf {
|
||||
case reflect.String:
|
||||
buf.WriteString(slice.Index(i).String())
|
||||
case reflect.Int, reflect.Int64:
|
||||
buf.WriteString(fmt.Sprint(slice.Index(i).Int()))
|
||||
case reflect.Uint, reflect.Uint64:
|
||||
buf.WriteString(fmt.Sprint(slice.Index(i).Uint()))
|
||||
case reflect.Float64:
|
||||
buf.WriteString(fmt.Sprint(slice.Index(i).Float()))
|
||||
case reflect.Bool:
|
||||
buf.WriteString(fmt.Sprint(slice.Index(i).Bool()))
|
||||
case reflectTime:
|
||||
buf.WriteString(slice.Index(i).Interface().(time.Time).Format(time.RFC3339))
|
||||
default:
|
||||
return fmt.Errorf("unsupported type '[]%s'", sliceOf)
|
||||
}
|
||||
buf.WriteString(delim)
|
||||
}
|
||||
key.SetValue(buf.String()[:buf.Len()-len(delim)])
|
||||
return nil
|
||||
}
|
||||
|
||||
// reflectWithProperType does the opposite thing as setWithProperType.
|
||||
func reflectWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow bool) error {
|
||||
switch t.Kind() {
|
||||
case reflect.String:
|
||||
key.SetValue(field.String())
|
||||
case reflect.Bool:
|
||||
key.SetValue(fmt.Sprint(field.Bool()))
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
key.SetValue(fmt.Sprint(field.Int()))
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
key.SetValue(fmt.Sprint(field.Uint()))
|
||||
case reflect.Float32, reflect.Float64:
|
||||
key.SetValue(fmt.Sprint(field.Float()))
|
||||
case reflectTime:
|
||||
key.SetValue(fmt.Sprint(field.Interface().(time.Time).Format(time.RFC3339)))
|
||||
case reflect.Slice:
|
||||
return reflectSliceWithProperType(key, field, delim, allowShadow)
|
||||
case reflect.Ptr:
|
||||
if !field.IsNil() {
|
||||
return reflectWithProperType(t.Elem(), key, field.Elem(), delim, allowShadow)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported type %q", t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CR: copied from encoding/json/encode.go with modifications of time.Time support.
|
||||
// TODO: add more test coverage.
|
||||
func isEmptyValue(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
return v.IsNil()
|
||||
case reflectTime:
|
||||
t, ok := v.Interface().(time.Time)
|
||||
return ok && t.IsZero()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// StructReflector is the interface implemented by struct types that can extract themselves into INI objects.
|
||||
type StructReflector interface {
|
||||
ReflectINIStruct(*File) error
|
||||
}
|
||||
|
||||
func (s *Section) reflectFrom(val reflect.Value) error {
|
||||
if val.Kind() == reflect.Ptr {
|
||||
val = val.Elem()
|
||||
}
|
||||
typ := val.Type()
|
||||
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
if !val.Field(i).CanInterface() {
|
||||
continue
|
||||
}
|
||||
|
||||
field := val.Field(i)
|
||||
tpField := typ.Field(i)
|
||||
|
||||
tag := tpField.Tag.Get("ini")
|
||||
if tag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
rawName, omitEmpty, allowShadow, allowNonUnique, extends := parseTagOptions(tag)
|
||||
if omitEmpty && isEmptyValue(field) {
|
||||
continue
|
||||
}
|
||||
|
||||
if r, ok := field.Interface().(StructReflector); ok {
|
||||
return r.ReflectINIStruct(s.f)
|
||||
}
|
||||
|
||||
fieldName := s.parseFieldName(tpField.Name, rawName)
|
||||
if len(fieldName) == 0 || !field.CanSet() {
|
||||
continue
|
||||
}
|
||||
|
||||
if extends && tpField.Anonymous && (tpField.Type.Kind() == reflect.Ptr || tpField.Type.Kind() == reflect.Struct) {
|
||||
if err := s.reflectFrom(field); err != nil {
|
||||
return fmt.Errorf("reflect from field %q: %v", fieldName, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct) ||
|
||||
(tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") {
|
||||
// Note: The only error here is section doesn't exist.
|
||||
sec, err := s.f.GetSection(fieldName)
|
||||
if err != nil {
|
||||
// Note: fieldName can never be empty here, ignore error.
|
||||
sec, _ = s.f.NewSection(fieldName)
|
||||
}
|
||||
|
||||
// Add comment from comment tag
|
||||
if len(sec.Comment) == 0 {
|
||||
sec.Comment = tpField.Tag.Get("comment")
|
||||
}
|
||||
|
||||
if err = sec.reflectFrom(field); err != nil {
|
||||
return fmt.Errorf("reflect from field %q: %v", fieldName, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if allowNonUnique && tpField.Type.Kind() == reflect.Slice {
|
||||
slice := field.Slice(0, field.Len())
|
||||
if field.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
sliceOf := field.Type().Elem().Kind()
|
||||
|
||||
for i := 0; i < field.Len(); i++ {
|
||||
if sliceOf != reflect.Struct && sliceOf != reflect.Ptr {
|
||||
return fmt.Errorf("field %q is not a slice of pointer or struct", fieldName)
|
||||
}
|
||||
|
||||
sec, err := s.f.NewSection(fieldName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add comment from comment tag
|
||||
if len(sec.Comment) == 0 {
|
||||
sec.Comment = tpField.Tag.Get("comment")
|
||||
}
|
||||
|
||||
if err := sec.reflectFrom(slice.Index(i)); err != nil {
|
||||
return fmt.Errorf("reflect from field %q: %v", fieldName, err)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Note: Same reason as section.
|
||||
key, err := s.GetKey(fieldName)
|
||||
if err != nil {
|
||||
key, _ = s.NewKey(fieldName, "")
|
||||
}
|
||||
|
||||
// Add comment from comment tag
|
||||
if len(key.Comment) == 0 {
|
||||
key.Comment = tpField.Tag.Get("comment")
|
||||
}
|
||||
|
||||
delim := parseDelim(tpField.Tag.Get("delim"))
|
||||
if err = reflectWithProperType(tpField.Type, key, field, delim, allowShadow); err != nil {
|
||||
return fmt.Errorf("reflect field %q: %v", fieldName, err)
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReflectFrom reflects section from given struct. It overwrites existing ones.
|
||||
func (s *Section) ReflectFrom(v interface{}) error {
|
||||
typ := reflect.TypeOf(v)
|
||||
val := reflect.ValueOf(v)
|
||||
|
||||
if s.name != DefaultSection && s.f.options.AllowNonUniqueSections &&
|
||||
(typ.Kind() == reflect.Slice || typ.Kind() == reflect.Ptr) {
|
||||
// Clear sections to make sure none exists before adding the new ones
|
||||
s.f.DeleteSection(s.name)
|
||||
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
sec, err := s.f.NewSection(s.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sec.reflectFrom(val.Elem())
|
||||
}
|
||||
|
||||
slice := val.Slice(0, val.Len())
|
||||
sliceOf := val.Type().Elem().Kind()
|
||||
if sliceOf != reflect.Ptr {
|
||||
return fmt.Errorf("not a slice of pointers")
|
||||
}
|
||||
|
||||
for i := 0; i < slice.Len(); i++ {
|
||||
sec, err := s.f.NewSection(s.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = sec.reflectFrom(slice.Index(i))
|
||||
if err != nil {
|
||||
return fmt.Errorf("reflect from %dth field: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
val = val.Elem()
|
||||
} else {
|
||||
return errors.New("not a pointer to a struct")
|
||||
}
|
||||
|
||||
return s.reflectFrom(val)
|
||||
}
|
||||
|
||||
// ReflectFrom reflects file from given struct.
|
||||
func (f *File) ReflectFrom(v interface{}) error {
|
||||
return f.Section("").ReflectFrom(v)
|
||||
}
|
||||
|
||||
// ReflectFromWithMapper reflects data sources from given struct with name mapper.
|
||||
func ReflectFromWithMapper(cfg *File, v interface{}, mapper NameMapper) error {
|
||||
cfg.NameMapper = mapper
|
||||
return cfg.ReflectFrom(v)
|
||||
}
|
||||
|
||||
// ReflectFrom reflects data sources from given struct.
|
||||
func ReflectFrom(cfg *File, v interface{}) error {
|
||||
return ReflectFromWithMapper(cfg, v, nil)
|
||||
}
|
||||
-3
@@ -2,9 +2,6 @@
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
package capabilities
|
||||
|
||||
import (
|
||||
|
||||
Generated
Vendored
+22
-11
@@ -40,7 +40,8 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"type": "function"
|
||||
}
|
||||
},
|
||||
"deprecated": true
|
||||
},
|
||||
{
|
||||
"name": "and",
|
||||
@@ -95,7 +96,8 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"type": "function"
|
||||
}
|
||||
},
|
||||
"deprecated": true
|
||||
},
|
||||
{
|
||||
"name": "array.concat",
|
||||
@@ -385,7 +387,8 @@
|
||||
"type": "array"
|
||||
},
|
||||
"type": "function"
|
||||
}
|
||||
},
|
||||
"deprecated": true
|
||||
},
|
||||
{
|
||||
"name": "cast_boolean",
|
||||
@@ -399,7 +402,8 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"type": "function"
|
||||
}
|
||||
},
|
||||
"deprecated": true
|
||||
},
|
||||
{
|
||||
"name": "cast_null",
|
||||
@@ -413,7 +417,8 @@
|
||||
"type": "null"
|
||||
},
|
||||
"type": "function"
|
||||
}
|
||||
},
|
||||
"deprecated": true
|
||||
},
|
||||
{
|
||||
"name": "cast_object",
|
||||
@@ -435,7 +440,8 @@
|
||||
"type": "object"
|
||||
},
|
||||
"type": "function"
|
||||
}
|
||||
},
|
||||
"deprecated": true
|
||||
},
|
||||
{
|
||||
"name": "cast_set",
|
||||
@@ -452,7 +458,8 @@
|
||||
"type": "set"
|
||||
},
|
||||
"type": "function"
|
||||
}
|
||||
},
|
||||
"deprecated": true
|
||||
},
|
||||
{
|
||||
"name": "cast_string",
|
||||
@@ -466,7 +473,8 @@
|
||||
"type": "string"
|
||||
},
|
||||
"type": "function"
|
||||
}
|
||||
},
|
||||
"deprecated": true
|
||||
},
|
||||
{
|
||||
"name": "ceil",
|
||||
@@ -2975,7 +2983,8 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"type": "function"
|
||||
}
|
||||
},
|
||||
"deprecated": true
|
||||
},
|
||||
{
|
||||
"name": "net.lookup_ip_addr",
|
||||
@@ -3493,7 +3502,8 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"type": "function"
|
||||
}
|
||||
},
|
||||
"deprecated": true
|
||||
},
|
||||
{
|
||||
"name": "regex.find_all_string_submatch_n",
|
||||
@@ -3808,7 +3818,8 @@
|
||||
"type": "set"
|
||||
},
|
||||
"type": "function"
|
||||
}
|
||||
},
|
||||
"deprecated": true
|
||||
},
|
||||
{
|
||||
"name": "sort",
|
||||
+4878
File diff suppressed because it is too large
Load Diff
+4896
File diff suppressed because it is too large
Load Diff
+4896
File diff suppressed because it is too large
Load Diff
+4896
File diff suppressed because it is too large
Load Diff
+4896
File diff suppressed because it is too large
Load Diff
+4916
File diff suppressed because it is too large
Load Diff
+4916
File diff suppressed because it is too large
Load Diff
+4916
File diff suppressed because it is too large
Load Diff
+4916
File diff suppressed because it is too large
Load Diff
+4916
File diff suppressed because it is too large
Load Diff
+654
-569
File diff suppressed because one or more lines are too long
BIN
Binary file not shown.
+1
-1
@@ -133,7 +133,7 @@ func (c *Compiler) removeUnusedCode() error {
|
||||
}
|
||||
caller, ok := c.funcs[callerName]
|
||||
if !ok {
|
||||
return fmt.Errorf("caller not found: %s (%s)", cg[i][0], callerName)
|
||||
continue // without a caller, it should get removed anyways (right?)
|
||||
}
|
||||
callee, ok := c.funcs[calleeName]
|
||||
if !ok {
|
||||
|
||||
+237
-192
@@ -32,7 +32,7 @@ const (
|
||||
opaWasmABIMinorVersionVar = "opa_wasm_abi_minor_version"
|
||||
)
|
||||
|
||||
// nolint: deadcode,varcheck
|
||||
// nolint: varcheck
|
||||
const (
|
||||
opaTypeNull int32 = iota + 1
|
||||
opaTypeBoolean
|
||||
@@ -90,6 +90,7 @@ var builtinsFunctions = map[string]string{
|
||||
ast.Floor.Name: "opa_arith_floor",
|
||||
ast.Rem.Name: "opa_arith_rem",
|
||||
ast.ArrayConcat.Name: "opa_array_concat",
|
||||
ast.ArrayFlatten.Name: "opa_array_flatten",
|
||||
ast.ArrayReverse.Name: "opa_array_reverse",
|
||||
ast.ArraySlice.Name: "opa_array_slice",
|
||||
ast.SetDiff.Name: "opa_set_diff",
|
||||
@@ -162,6 +163,7 @@ var builtinsFunctions = map[string]string{
|
||||
ast.TrimRight.Name: "opa_strings_trim_right",
|
||||
ast.TrimSuffix.Name: "opa_strings_trim_suffix",
|
||||
ast.TrimSpace.Name: "opa_strings_trim_space",
|
||||
ast.InternalTemplateString.Name: "opa_template_string",
|
||||
ast.NumbersRange.Name: "opa_numbers_range",
|
||||
ast.ToNumber.Name: "opa_to_number",
|
||||
ast.WalkBuiltin.Name: "opa_value_transitive_closure",
|
||||
@@ -414,7 +416,7 @@ func (c *Compiler) initModule() error {
|
||||
},
|
||||
},
|
||||
},
|
||||
Init: bytes.Repeat([]byte{0}, int(heapBase-offset)),
|
||||
Init: make([]byte, int(heapBase-offset)),
|
||||
})
|
||||
|
||||
return nil
|
||||
@@ -1058,9 +1060,11 @@ func (c *Compiler) compileBlock(block *ir.Block) ([]instruction.Instruction, err
|
||||
},
|
||||
})
|
||||
case *ir.AssignIntStmt:
|
||||
instrs = append(instrs, instruction.GetLocal{Index: c.local(stmt.Target)})
|
||||
instrs = append(instrs, instruction.I64Const{Value: stmt.Value})
|
||||
instrs = append(instrs, instruction.Call{Index: c.function(opaValueNumberSetInt)})
|
||||
instrs = append(instrs,
|
||||
instruction.GetLocal{Index: c.local(stmt.Target)},
|
||||
instruction.I64Const{Value: stmt.Value},
|
||||
instruction.Call{Index: c.function(opaValueNumberSetInt)},
|
||||
)
|
||||
case *ir.ScanStmt:
|
||||
if err := c.compileScan(stmt, &instrs); err != nil {
|
||||
return nil, err
|
||||
@@ -1073,12 +1077,14 @@ func (c *Compiler) compileBlock(block *ir.Block) ([]instruction.Instruction, err
|
||||
}
|
||||
case *ir.DotStmt:
|
||||
if loc, ok := stmt.Source.Value.(ir.Local); ok {
|
||||
instrs = append(instrs, instruction.GetLocal{Index: c.local(loc)})
|
||||
instrs = append(instrs, c.instrRead(stmt.Key))
|
||||
instrs = append(instrs, instruction.Call{Index: c.function(opaValueGet)})
|
||||
instrs = append(instrs, instruction.TeeLocal{Index: c.local(stmt.Target)})
|
||||
instrs = append(instrs, instruction.I32Eqz{})
|
||||
instrs = append(instrs, instruction.BrIf{Index: 0})
|
||||
instrs = append(instrs,
|
||||
instruction.GetLocal{Index: c.local(loc)},
|
||||
c.instrRead(stmt.Key),
|
||||
instruction.Call{Index: c.function(opaValueGet)},
|
||||
instruction.TeeLocal{Index: c.local(stmt.Target)},
|
||||
instruction.I32Eqz{},
|
||||
instruction.BrIf{Index: 0},
|
||||
)
|
||||
} else {
|
||||
// Booleans and string sources would lead to the BrIf (since opa_value_get
|
||||
// on them returns 0), so let's skip trying that.
|
||||
@@ -1086,97 +1092,131 @@ func (c *Compiler) compileBlock(block *ir.Block) ([]instruction.Instruction, err
|
||||
break
|
||||
}
|
||||
case *ir.LenStmt:
|
||||
instrs = append(instrs, c.instrRead(stmt.Source))
|
||||
instrs = append(instrs, instruction.Call{Index: c.function(opaValueLength)})
|
||||
instrs = append(instrs, instruction.Call{Index: c.function(opaNumberSize)})
|
||||
instrs = append(instrs, instruction.SetLocal{Index: c.local(stmt.Target)})
|
||||
instrs = append(instrs,
|
||||
c.instrRead(stmt.Source),
|
||||
instruction.Call{Index: c.function(opaValueLength)},
|
||||
instruction.Call{Index: c.function(opaNumberSize)},
|
||||
instruction.SetLocal{Index: c.local(stmt.Target)},
|
||||
)
|
||||
case *ir.EqualStmt:
|
||||
instrs = append(instrs, c.instrRead(stmt.A))
|
||||
instrs = append(instrs, c.instrRead(stmt.B))
|
||||
instrs = append(instrs, instruction.Call{Index: c.function(opaValueCompare)})
|
||||
instrs = append(instrs, instruction.BrIf{Index: 0})
|
||||
instrs = append(instrs,
|
||||
c.instrRead(stmt.A),
|
||||
c.instrRead(stmt.B),
|
||||
instruction.Call{Index: c.function(opaValueCompare)},
|
||||
instruction.BrIf{Index: 0},
|
||||
)
|
||||
case *ir.NotEqualStmt:
|
||||
instrs = append(instrs, c.instrRead(stmt.A))
|
||||
instrs = append(instrs, c.instrRead(stmt.B))
|
||||
instrs = append(instrs, instruction.Call{Index: c.function(opaValueCompare)})
|
||||
instrs = append(instrs, instruction.I32Eqz{})
|
||||
instrs = append(instrs, instruction.BrIf{Index: 0})
|
||||
instrs = append(instrs,
|
||||
c.instrRead(stmt.A),
|
||||
c.instrRead(stmt.B),
|
||||
instruction.Call{Index: c.function(opaValueCompare)},
|
||||
instruction.I32Eqz{},
|
||||
instruction.BrIf{Index: 0},
|
||||
)
|
||||
case *ir.MakeNullStmt:
|
||||
instrs = append(instrs, instruction.Call{Index: c.function(opaNull)})
|
||||
instrs = append(instrs, instruction.SetLocal{Index: c.local(stmt.Target)})
|
||||
instrs = append(instrs,
|
||||
instruction.Call{Index: c.function(opaNull)},
|
||||
instruction.SetLocal{Index: c.local(stmt.Target)},
|
||||
)
|
||||
case *ir.MakeNumberIntStmt:
|
||||
instrs = append(instrs, instruction.I64Const{Value: stmt.Value})
|
||||
instrs = append(instrs, instruction.Call{Index: c.function(opaNumberInt)})
|
||||
instrs = append(instrs, instruction.SetLocal{Index: c.local(stmt.Target)})
|
||||
instrs = append(instrs,
|
||||
instruction.I64Const{Value: stmt.Value},
|
||||
instruction.Call{Index: c.function(opaNumberInt)},
|
||||
instruction.SetLocal{Index: c.local(stmt.Target)},
|
||||
)
|
||||
case *ir.MakeNumberRefStmt:
|
||||
instrs = append(instrs, instruction.I32Const{Value: c.stringAddr(stmt.Index)})
|
||||
instrs = append(instrs, instruction.I32Const{Value: int32(len(c.policy.Static.Strings[stmt.Index].Value))})
|
||||
instrs = append(instrs, instruction.Call{Index: c.function(opaNumberRef)})
|
||||
instrs = append(instrs, instruction.SetLocal{Index: c.local(stmt.Target)})
|
||||
instrs = append(instrs,
|
||||
instruction.I32Const{Value: c.stringAddr(stmt.Index)},
|
||||
instruction.I32Const{Value: int32(len(c.policy.Static.Strings[stmt.Index].Value))},
|
||||
instruction.Call{Index: c.function(opaNumberRef)},
|
||||
instruction.SetLocal{Index: c.local(stmt.Target)},
|
||||
)
|
||||
case *ir.MakeArrayStmt:
|
||||
instrs = append(instrs, instruction.I32Const{Value: stmt.Capacity})
|
||||
instrs = append(instrs, instruction.Call{Index: c.function(opaArrayWithCap)})
|
||||
instrs = append(instrs, instruction.SetLocal{Index: c.local(stmt.Target)})
|
||||
instrs = append(instrs,
|
||||
instruction.I32Const{Value: stmt.Capacity},
|
||||
instruction.Call{Index: c.function(opaArrayWithCap)},
|
||||
instruction.SetLocal{Index: c.local(stmt.Target)},
|
||||
)
|
||||
case *ir.MakeObjectStmt:
|
||||
instrs = append(instrs, instruction.Call{Index: c.function(opaObject)})
|
||||
instrs = append(instrs, instruction.SetLocal{Index: c.local(stmt.Target)})
|
||||
instrs = append(instrs,
|
||||
instruction.Call{Index: c.function(opaObject)},
|
||||
instruction.SetLocal{Index: c.local(stmt.Target)},
|
||||
)
|
||||
case *ir.MakeSetStmt:
|
||||
instrs = append(instrs, instruction.Call{Index: c.function(opaSet)})
|
||||
instrs = append(instrs, instruction.SetLocal{Index: c.local(stmt.Target)})
|
||||
instrs = append(instrs,
|
||||
instruction.Call{Index: c.function(opaSet)},
|
||||
instruction.SetLocal{Index: c.local(stmt.Target)},
|
||||
)
|
||||
case *ir.IsArrayStmt:
|
||||
if loc, ok := stmt.Source.Value.(ir.Local); ok {
|
||||
instrs = append(instrs, instruction.GetLocal{Index: c.local(loc)})
|
||||
instrs = append(instrs, instruction.Call{Index: c.function(opaValueType)})
|
||||
instrs = append(instrs, instruction.I32Const{Value: opaTypeArray})
|
||||
instrs = append(instrs, instruction.I32Ne{})
|
||||
instrs = append(instrs, instruction.BrIf{Index: 0})
|
||||
instrs = append(instrs,
|
||||
instruction.GetLocal{Index: c.local(loc)},
|
||||
instruction.Call{Index: c.function(opaValueType)},
|
||||
instruction.I32Const{Value: opaTypeArray},
|
||||
instruction.I32Ne{},
|
||||
instruction.BrIf{Index: 0},
|
||||
)
|
||||
} else {
|
||||
instrs = append(instrs, instruction.Br{Index: 0})
|
||||
break
|
||||
}
|
||||
case *ir.IsObjectStmt:
|
||||
if loc, ok := stmt.Source.Value.(ir.Local); ok {
|
||||
instrs = append(instrs, instruction.GetLocal{Index: c.local(loc)})
|
||||
instrs = append(instrs, instruction.Call{Index: c.function(opaValueType)})
|
||||
instrs = append(instrs, instruction.I32Const{Value: opaTypeObject})
|
||||
instrs = append(instrs, instruction.I32Ne{})
|
||||
instrs = append(instrs, instruction.BrIf{Index: 0})
|
||||
instrs = append(instrs,
|
||||
instruction.GetLocal{Index: c.local(loc)},
|
||||
instruction.Call{Index: c.function(opaValueType)},
|
||||
instruction.I32Const{Value: opaTypeObject},
|
||||
instruction.I32Ne{},
|
||||
instruction.BrIf{Index: 0},
|
||||
)
|
||||
} else {
|
||||
instrs = append(instrs, instruction.Br{Index: 0})
|
||||
break
|
||||
}
|
||||
case *ir.IsSetStmt:
|
||||
if loc, ok := stmt.Source.Value.(ir.Local); ok {
|
||||
instrs = append(instrs, instruction.GetLocal{Index: c.local(loc)})
|
||||
instrs = append(instrs, instruction.Call{Index: c.function(opaValueType)})
|
||||
instrs = append(instrs, instruction.I32Const{Value: opaTypeSet})
|
||||
instrs = append(instrs, instruction.I32Ne{})
|
||||
instrs = append(instrs, instruction.BrIf{Index: 0})
|
||||
instrs = append(instrs,
|
||||
instruction.GetLocal{Index: c.local(loc)},
|
||||
instruction.Call{Index: c.function(opaValueType)},
|
||||
instruction.I32Const{Value: opaTypeSet},
|
||||
instruction.I32Ne{},
|
||||
instruction.BrIf{Index: 0},
|
||||
)
|
||||
} else {
|
||||
instrs = append(instrs, instruction.Br{Index: 0})
|
||||
break
|
||||
}
|
||||
case *ir.IsUndefinedStmt:
|
||||
instrs = append(instrs, instruction.GetLocal{Index: c.local(stmt.Source)})
|
||||
instrs = append(instrs, instruction.I32Const{Value: 0})
|
||||
instrs = append(instrs, instruction.I32Ne{})
|
||||
instrs = append(instrs, instruction.BrIf{Index: 0})
|
||||
instrs = append(instrs,
|
||||
instruction.GetLocal{Index: c.local(stmt.Source)},
|
||||
instruction.I32Const{Value: 0},
|
||||
instruction.I32Ne{},
|
||||
instruction.BrIf{Index: 0},
|
||||
)
|
||||
case *ir.ResetLocalStmt:
|
||||
instrs = append(instrs, instruction.I32Const{Value: 0})
|
||||
instrs = append(instrs, instruction.SetLocal{Index: c.local(stmt.Target)})
|
||||
instrs = append(instrs,
|
||||
instruction.I32Const{Value: 0},
|
||||
instruction.SetLocal{Index: c.local(stmt.Target)},
|
||||
)
|
||||
case *ir.IsDefinedStmt:
|
||||
instrs = append(instrs, instruction.GetLocal{Index: c.local(stmt.Source)})
|
||||
instrs = append(instrs, instruction.I32Eqz{})
|
||||
instrs = append(instrs, instruction.BrIf{Index: 0})
|
||||
instrs = append(instrs,
|
||||
instruction.GetLocal{Index: c.local(stmt.Source)},
|
||||
instruction.I32Eqz{},
|
||||
instruction.BrIf{Index: 0},
|
||||
)
|
||||
case *ir.ArrayAppendStmt:
|
||||
instrs = append(instrs, instruction.GetLocal{Index: c.local(stmt.Array)})
|
||||
instrs = append(instrs, c.instrRead(stmt.Value))
|
||||
instrs = append(instrs, instruction.Call{Index: c.function(opaArrayAppend)})
|
||||
instrs = append(instrs,
|
||||
instruction.GetLocal{Index: c.local(stmt.Array)},
|
||||
c.instrRead(stmt.Value),
|
||||
instruction.Call{Index: c.function(opaArrayAppend)},
|
||||
)
|
||||
case *ir.ObjectInsertStmt:
|
||||
instrs = append(instrs, instruction.GetLocal{Index: c.local(stmt.Object)})
|
||||
instrs = append(instrs, c.instrRead(stmt.Key))
|
||||
instrs = append(instrs, c.instrRead(stmt.Value))
|
||||
instrs = append(instrs, instruction.Call{Index: c.function(opaObjectInsert)})
|
||||
instrs = append(instrs,
|
||||
instruction.GetLocal{Index: c.local(stmt.Object)},
|
||||
c.instrRead(stmt.Key),
|
||||
c.instrRead(stmt.Value),
|
||||
instruction.Call{Index: c.function(opaObjectInsert)},
|
||||
)
|
||||
case *ir.ObjectInsertOnceStmt:
|
||||
tmp := c.genLocal()
|
||||
instrs = append(instrs, instruction.Block{
|
||||
@@ -1203,14 +1243,18 @@ func (c *Compiler) compileBlock(block *ir.Block) ([]instruction.Instruction, err
|
||||
},
|
||||
})
|
||||
case *ir.ObjectMergeStmt:
|
||||
instrs = append(instrs, instruction.GetLocal{Index: c.local(stmt.A)})
|
||||
instrs = append(instrs, instruction.GetLocal{Index: c.local(stmt.B)})
|
||||
instrs = append(instrs, instruction.Call{Index: c.function(opaValueMerge)})
|
||||
instrs = append(instrs, instruction.SetLocal{Index: c.local(stmt.Target)})
|
||||
instrs = append(instrs,
|
||||
instruction.GetLocal{Index: c.local(stmt.A)},
|
||||
instruction.GetLocal{Index: c.local(stmt.B)},
|
||||
instruction.Call{Index: c.function(opaValueMerge)},
|
||||
instruction.SetLocal{Index: c.local(stmt.Target)},
|
||||
)
|
||||
case *ir.SetAddStmt:
|
||||
instrs = append(instrs, instruction.GetLocal{Index: c.local(stmt.Set)})
|
||||
instrs = append(instrs, c.instrRead(stmt.Value))
|
||||
instrs = append(instrs, instruction.Call{Index: c.function(opaSetAdd)})
|
||||
instrs = append(instrs,
|
||||
instruction.GetLocal{Index: c.local(stmt.Set)},
|
||||
c.instrRead(stmt.Value),
|
||||
instruction.Call{Index: c.function(opaSetAdd)},
|
||||
)
|
||||
default:
|
||||
var buf bytes.Buffer
|
||||
err := ir.Pretty(&buf, stmt)
|
||||
@@ -1226,8 +1270,7 @@ func (c *Compiler) compileBlock(block *ir.Block) ([]instruction.Instruction, err
|
||||
|
||||
func (c *Compiler) compileScan(scan *ir.ScanStmt, result *[]instruction.Instruction) error {
|
||||
var instrs = *result
|
||||
instrs = append(instrs, instruction.I32Const{Value: 0})
|
||||
instrs = append(instrs, instruction.SetLocal{Index: c.local(scan.Key)})
|
||||
instrs = append(instrs, instruction.I32Const{Value: 0}, instruction.SetLocal{Index: c.local(scan.Key)})
|
||||
body, err := c.compileScanBlock(scan)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1242,23 +1285,22 @@ func (c *Compiler) compileScan(scan *ir.ScanStmt, result *[]instruction.Instruct
|
||||
}
|
||||
|
||||
func (c *Compiler) compileScanBlock(scan *ir.ScanStmt) ([]instruction.Instruction, error) {
|
||||
var instrs []instruction.Instruction
|
||||
|
||||
// Execute iterator.
|
||||
instrs = append(instrs, instruction.GetLocal{Index: c.local(scan.Source)})
|
||||
instrs = append(instrs, instruction.GetLocal{Index: c.local(scan.Key)})
|
||||
instrs = append(instrs, instruction.Call{Index: c.function(opaValueIter)})
|
||||
|
||||
// Check for emptiness.
|
||||
instrs = append(instrs, instruction.TeeLocal{Index: c.local(scan.Key)})
|
||||
instrs = append(instrs, instruction.I32Eqz{})
|
||||
instrs = append(instrs, instruction.BrIf{Index: 1})
|
||||
|
||||
// Load value.
|
||||
instrs = append(instrs, instruction.GetLocal{Index: c.local(scan.Source)})
|
||||
instrs = append(instrs, instruction.GetLocal{Index: c.local(scan.Key)})
|
||||
instrs = append(instrs, instruction.Call{Index: c.function(opaValueGet)})
|
||||
instrs = append(instrs, instruction.SetLocal{Index: c.local(scan.Value)})
|
||||
//nolint:prealloc // instruction list is known and fixed, clearer as slice literal
|
||||
instrs := []instruction.Instruction{
|
||||
// Execute iterator.
|
||||
instruction.GetLocal{Index: c.local(scan.Source)},
|
||||
instruction.GetLocal{Index: c.local(scan.Key)},
|
||||
instruction.Call{Index: c.function(opaValueIter)},
|
||||
// Check for emptiness.
|
||||
instruction.TeeLocal{Index: c.local(scan.Key)},
|
||||
instruction.I32Eqz{},
|
||||
instruction.BrIf{Index: 1},
|
||||
// Load value.
|
||||
instruction.GetLocal{Index: c.local(scan.Source)},
|
||||
instruction.GetLocal{Index: c.local(scan.Key)},
|
||||
instruction.Call{Index: c.function(opaValueGet)},
|
||||
instruction.SetLocal{Index: c.local(scan.Value)},
|
||||
}
|
||||
|
||||
// Loop body.
|
||||
nested, err := c.compileBlock(scan.Block)
|
||||
@@ -1278,8 +1320,7 @@ func (c *Compiler) compileNot(not *ir.NotStmt, result *[]instruction.Instruction
|
||||
|
||||
// generate and initialize condition variable
|
||||
cond := c.genLocal()
|
||||
instrs = append(instrs, instruction.I32Const{Value: 1})
|
||||
instrs = append(instrs, instruction.SetLocal{Index: cond})
|
||||
instrs = append(instrs, instruction.I32Const{Value: 1}, instruction.SetLocal{Index: cond})
|
||||
|
||||
nested, err := c.compileBlock(not.Block)
|
||||
if err != nil {
|
||||
@@ -1287,14 +1328,15 @@ func (c *Compiler) compileNot(not *ir.NotStmt, result *[]instruction.Instruction
|
||||
}
|
||||
|
||||
// unset condition variable if end of block is reached
|
||||
nested = append(nested, instruction.I32Const{Value: 0})
|
||||
nested = append(nested, instruction.SetLocal{Index: cond})
|
||||
instrs = append(instrs, instruction.Block{Instrs: nested})
|
||||
|
||||
// break out of block if condition variable was unset
|
||||
instrs = append(instrs, instruction.GetLocal{Index: cond})
|
||||
instrs = append(instrs, instruction.I32Eqz{})
|
||||
instrs = append(instrs, instruction.BrIf{Index: 0})
|
||||
instrs = append(instrs, instruction.Block{Instrs: append(nested,
|
||||
instruction.I32Const{Value: 0},
|
||||
instruction.SetLocal{Index: cond},
|
||||
)},
|
||||
// break out of block if condition variable was unset
|
||||
instruction.GetLocal{Index: cond},
|
||||
instruction.I32Eqz{},
|
||||
instruction.BrIf{Index: 0},
|
||||
)
|
||||
|
||||
*result = instrs
|
||||
return nil
|
||||
@@ -1304,34 +1346,36 @@ func (c *Compiler) compileWithStmt(with *ir.WithStmt, result *[]instruction.Inst
|
||||
|
||||
var instrs = *result
|
||||
save := c.genLocal()
|
||||
instrs = append(instrs, instruction.Call{Index: c.function(opaMemoizePush)})
|
||||
instrs = append(instrs, instruction.GetLocal{Index: c.local(with.Local)})
|
||||
instrs = append(instrs, instruction.SetLocal{Index: save})
|
||||
instrs = append(instrs,
|
||||
instruction.Call{Index: c.function(opaMemoizePush)},
|
||||
instruction.GetLocal{Index: c.local(with.Local)},
|
||||
instruction.SetLocal{Index: save},
|
||||
)
|
||||
|
||||
if len(with.Path) == 0 {
|
||||
instrs = append(instrs, c.instrRead(with.Value))
|
||||
instrs = append(instrs, instruction.SetLocal{Index: c.local(with.Local)})
|
||||
instrs = append(instrs, c.instrRead(with.Value), instruction.SetLocal{Index: c.local(with.Local)})
|
||||
} else {
|
||||
instrs = c.compileUpsert(with.Local, with.Path, with.Value, with.Location, instrs)
|
||||
}
|
||||
|
||||
undefined := c.genLocal()
|
||||
instrs = append(instrs, instruction.I32Const{Value: 1})
|
||||
instrs = append(instrs, instruction.SetLocal{Index: undefined})
|
||||
instrs = append(instrs, instruction.I32Const{Value: 1}, instruction.SetLocal{Index: undefined})
|
||||
|
||||
nested, err := c.compileBlock(with.Block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nested = append(nested, instruction.I32Const{Value: 0})
|
||||
nested = append(nested, instruction.SetLocal{Index: undefined})
|
||||
instrs = append(instrs, instruction.Block{Instrs: nested})
|
||||
instrs = append(instrs, instruction.GetLocal{Index: save})
|
||||
instrs = append(instrs, instruction.SetLocal{Index: c.local(with.Local)})
|
||||
instrs = append(instrs, instruction.Call{Index: c.function(opaMemoizePop)})
|
||||
instrs = append(instrs, instruction.GetLocal{Index: undefined})
|
||||
instrs = append(instrs, instruction.BrIf{Index: 0})
|
||||
nested = append(nested, instruction.I32Const{Value: 0}, instruction.SetLocal{Index: undefined})
|
||||
|
||||
instrs = append(instrs,
|
||||
instruction.Block{Instrs: nested},
|
||||
instruction.GetLocal{Index: save},
|
||||
instruction.SetLocal{Index: c.local(with.Local)},
|
||||
instruction.Call{Index: c.function(opaMemoizePop)},
|
||||
instruction.GetLocal{Index: undefined},
|
||||
instruction.BrIf{Index: 0},
|
||||
)
|
||||
|
||||
*result = instrs
|
||||
|
||||
@@ -1339,37 +1383,38 @@ func (c *Compiler) compileWithStmt(with *ir.WithStmt, result *[]instruction.Inst
|
||||
}
|
||||
|
||||
func (c *Compiler) compileUpsert(local ir.Local, path []int, value ir.Operand, _ ir.Location, instrs []instruction.Instruction) []instruction.Instruction {
|
||||
|
||||
lcopy := c.genLocal() // holds copy of local
|
||||
instrs = append(instrs, instruction.GetLocal{Index: c.local(local)})
|
||||
instrs = append(instrs, instruction.SetLocal{Index: lcopy})
|
||||
|
||||
// Shallow copy the local if defined otherwise initialize to an empty object.
|
||||
instrs = append(instrs, instruction.Block{
|
||||
Instrs: []instruction.Instruction{
|
||||
instruction.Block{Instrs: []instruction.Instruction{
|
||||
instruction.GetLocal{Index: lcopy},
|
||||
instruction.I32Eqz{},
|
||||
instruction.BrIf{Index: 0},
|
||||
instruction.GetLocal{Index: lcopy},
|
||||
instruction.Call{Index: c.function(opaValueShallowCopy)},
|
||||
instrs = append(instrs,
|
||||
instruction.GetLocal{Index: c.local(local)},
|
||||
instruction.SetLocal{Index: lcopy},
|
||||
// Shallow copy the local if defined otherwise initialize to an empty object.
|
||||
instruction.Block{
|
||||
Instrs: []instruction.Instruction{
|
||||
instruction.Block{Instrs: []instruction.Instruction{
|
||||
instruction.GetLocal{Index: lcopy},
|
||||
instruction.I32Eqz{},
|
||||
instruction.BrIf{Index: 0},
|
||||
instruction.GetLocal{Index: lcopy},
|
||||
instruction.Call{Index: c.function(opaValueShallowCopy)},
|
||||
instruction.TeeLocal{Index: lcopy},
|
||||
instruction.SetLocal{Index: c.local(local)},
|
||||
instruction.Br{Index: 1},
|
||||
}},
|
||||
instruction.Call{Index: c.function(opaObject)},
|
||||
instruction.TeeLocal{Index: lcopy},
|
||||
instruction.SetLocal{Index: c.local(local)},
|
||||
instruction.Br{Index: 1},
|
||||
}},
|
||||
instruction.Call{Index: c.function(opaObject)},
|
||||
instruction.TeeLocal{Index: lcopy},
|
||||
instruction.SetLocal{Index: c.local(local)},
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
// Initialize the locals that specify the path of the upsert operation.
|
||||
lpath := make(map[int]uint32, len(path))
|
||||
|
||||
for i := range path {
|
||||
lpath[i] = c.genLocal()
|
||||
instrs = append(instrs, instruction.I32Const{Value: c.opaStringAddr(path[i])})
|
||||
instrs = append(instrs, instruction.SetLocal{Index: lpath[i]})
|
||||
instrs = append(instrs,
|
||||
instruction.I32Const{Value: c.opaStringAddr(path[i])},
|
||||
instruction.SetLocal{Index: lpath[i]},
|
||||
)
|
||||
}
|
||||
|
||||
// Generate a block that traverses the path of the upsert operation,
|
||||
@@ -1379,36 +1424,34 @@ func (c *Compiler) compileUpsert(local ir.Local, path []int, value ir.Operand, _
|
||||
ltemp := c.genLocal()
|
||||
|
||||
for i := range len(path) - 1 {
|
||||
|
||||
// Lookup the next part of the path.
|
||||
inner = append(inner, instruction.GetLocal{Index: lcopy})
|
||||
inner = append(inner, instruction.GetLocal{Index: lpath[i]})
|
||||
inner = append(inner, instruction.Call{Index: c.function(opaValueGet)})
|
||||
inner = append(inner, instruction.SetLocal{Index: ltemp})
|
||||
|
||||
// If the next node is missing, break.
|
||||
inner = append(inner, instruction.GetLocal{Index: ltemp})
|
||||
inner = append(inner, instruction.I32Eqz{})
|
||||
inner = append(inner, instruction.BrIf{Index: uint32(i)})
|
||||
|
||||
// If the next node is not an object, break.
|
||||
inner = append(inner, instruction.GetLocal{Index: ltemp})
|
||||
inner = append(inner, instruction.Call{Index: c.function(opaValueType)})
|
||||
inner = append(inner, instruction.I32Const{Value: opaTypeObject})
|
||||
inner = append(inner, instruction.I32Ne{})
|
||||
inner = append(inner, instruction.BrIf{Index: uint32(i)})
|
||||
|
||||
// Otherwise, shallow copy the next node node and insert into the copy
|
||||
// before continuing.
|
||||
inner = append(inner, instruction.GetLocal{Index: ltemp})
|
||||
inner = append(inner, instruction.Call{Index: c.function(opaValueShallowCopy)})
|
||||
inner = append(inner, instruction.SetLocal{Index: ltemp})
|
||||
inner = append(inner, instruction.GetLocal{Index: lcopy})
|
||||
inner = append(inner, instruction.GetLocal{Index: lpath[i]})
|
||||
inner = append(inner, instruction.GetLocal{Index: ltemp})
|
||||
inner = append(inner, instruction.Call{Index: c.function(opaObjectInsert)})
|
||||
inner = append(inner, instruction.GetLocal{Index: ltemp})
|
||||
inner = append(inner, instruction.SetLocal{Index: lcopy})
|
||||
inner = append(inner,
|
||||
// Lookup the next part of the path.
|
||||
instruction.GetLocal{Index: lcopy},
|
||||
instruction.GetLocal{Index: lpath[i]},
|
||||
instruction.Call{Index: c.function(opaValueGet)},
|
||||
instruction.SetLocal{Index: ltemp},
|
||||
// If the next node is missing, break.
|
||||
instruction.GetLocal{Index: ltemp},
|
||||
instruction.I32Eqz{},
|
||||
instruction.BrIf{Index: uint32(i)},
|
||||
// If the next node is not an object, break.
|
||||
instruction.GetLocal{Index: ltemp},
|
||||
instruction.Call{Index: c.function(opaValueType)},
|
||||
instruction.I32Const{Value: opaTypeObject},
|
||||
instruction.I32Ne{},
|
||||
instruction.BrIf{Index: uint32(i)},
|
||||
// Otherwise, shallow copy the next node node and insert into the copy
|
||||
// before continuing.
|
||||
instruction.GetLocal{Index: ltemp},
|
||||
instruction.Call{Index: c.function(opaValueShallowCopy)},
|
||||
instruction.SetLocal{Index: ltemp},
|
||||
instruction.GetLocal{Index: lcopy},
|
||||
instruction.GetLocal{Index: lpath[i]},
|
||||
instruction.GetLocal{Index: ltemp},
|
||||
instruction.Call{Index: c.function(opaObjectInsert)},
|
||||
instruction.GetLocal{Index: ltemp},
|
||||
instruction.SetLocal{Index: lcopy},
|
||||
)
|
||||
}
|
||||
|
||||
inner = append(inner, instruction.Br{Index: uint32(len(path) - 1)})
|
||||
@@ -1418,31 +1461,33 @@ func (c *Compiler) compileUpsert(local ir.Local, path []int, value ir.Operand, _
|
||||
lval := c.genLocal()
|
||||
|
||||
for i := range len(path) - 1 {
|
||||
block = append(block, instruction.Block{Instrs: inner})
|
||||
block = append(block, instruction.Call{Index: c.function(opaObject)})
|
||||
block = append(block, instruction.SetLocal{Index: lval})
|
||||
block = append(block, instruction.GetLocal{Index: lcopy})
|
||||
block = append(block, instruction.GetLocal{Index: lpath[i]})
|
||||
block = append(block, instruction.GetLocal{Index: lval})
|
||||
block = append(block, instruction.Call{Index: c.function(opaObjectInsert)})
|
||||
block = append(block, instruction.GetLocal{Index: lval})
|
||||
block = append(block, instruction.SetLocal{Index: lcopy})
|
||||
block = append(block,
|
||||
instruction.Block{Instrs: inner},
|
||||
instruction.Call{Index: c.function(opaObject)},
|
||||
instruction.SetLocal{Index: lval},
|
||||
instruction.GetLocal{Index: lcopy},
|
||||
instruction.GetLocal{Index: lpath[i]},
|
||||
instruction.GetLocal{Index: lval},
|
||||
instruction.Call{Index: c.function(opaObjectInsert)},
|
||||
instruction.GetLocal{Index: lval},
|
||||
instruction.SetLocal{Index: lcopy},
|
||||
)
|
||||
inner = block
|
||||
block = nil
|
||||
}
|
||||
|
||||
// Finish by inserting the statement's value into the shallow copied node.
|
||||
instrs = append(instrs, instruction.Block{Instrs: inner})
|
||||
instrs = append(instrs, instruction.GetLocal{Index: lcopy})
|
||||
instrs = append(instrs, instruction.GetLocal{Index: lpath[len(path)-1]})
|
||||
instrs = append(instrs, c.instrRead(value))
|
||||
instrs = append(instrs, instruction.Call{Index: c.function(opaObjectInsert)})
|
||||
|
||||
return instrs
|
||||
return append(instrs,
|
||||
instruction.Block{Instrs: inner},
|
||||
instruction.GetLocal{Index: lcopy},
|
||||
instruction.GetLocal{Index: lpath[len(path)-1]},
|
||||
c.instrRead(value),
|
||||
instruction.Call{Index: c.function(opaObjectInsert)},
|
||||
)
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
@@ -1515,7 +1560,7 @@ func (c *Compiler) compileCallStmt(stmt *ir.CallStmt, result *[]instruction.Inst
|
||||
|
||||
func (c *Compiler) compileInternalCall(stmt *ir.CallStmt, index uint32, result *[]instruction.Instruction) error {
|
||||
|
||||
instrs := []instruction.Instruction{}
|
||||
instrs := make([]instruction.Instruction, 0, len(stmt.Args)+4)
|
||||
|
||||
// Prepare function args and call.
|
||||
for _, arg := range stmt.Args {
|
||||
|
||||
-176
@@ -1,176 +0,0 @@
|
||||
// Copyright 2020 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package config implements helper functions to parse OPA's configuration.
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/open-policy-agent/opa/internal/strvals"
|
||||
"github.com/open-policy-agent/opa/v1/keys"
|
||||
"github.com/open-policy-agent/opa/v1/logging"
|
||||
"github.com/open-policy-agent/opa/v1/plugins/rest"
|
||||
"github.com/open-policy-agent/opa/v1/tracing"
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// ServiceOptions stores the options passed to ParseServicesConfig
|
||||
type ServiceOptions struct {
|
||||
Raw json.RawMessage
|
||||
AuthPlugin rest.AuthPluginLookupFunc
|
||||
Keys map[string]*keys.Config
|
||||
Logger logging.Logger
|
||||
DistributedTacingOpts tracing.Options
|
||||
}
|
||||
|
||||
// ParseServicesConfig returns a set of named service clients. The service
|
||||
// clients can be specified either as an array or as a map. Some systems (e.g.,
|
||||
// Helm) do not have proper support for configuration values nested under
|
||||
// arrays, so just support both here.
|
||||
func ParseServicesConfig(opts ServiceOptions) (map[string]rest.Client, error) {
|
||||
|
||||
services := map[string]rest.Client{}
|
||||
|
||||
var arr []json.RawMessage
|
||||
var obj map[string]json.RawMessage
|
||||
|
||||
if err := util.Unmarshal(opts.Raw, &arr); err == nil {
|
||||
for _, s := range arr {
|
||||
client, err := rest.New(s, opts.Keys, rest.AuthPluginLookup(opts.AuthPlugin), rest.Logger(opts.Logger), rest.DistributedTracingOpts(opts.DistributedTacingOpts))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
services[client.Service()] = client
|
||||
}
|
||||
} else if util.Unmarshal(opts.Raw, &obj) == nil {
|
||||
for k := range obj {
|
||||
client, err := rest.New(obj[k], opts.Keys, rest.Name(k), rest.AuthPluginLookup(opts.AuthPlugin), rest.Logger(opts.Logger), rest.DistributedTracingOpts(opts.DistributedTacingOpts))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
services[client.Service()] = client
|
||||
}
|
||||
} else {
|
||||
// Return error from array decode as that is the default format.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return services, nil
|
||||
}
|
||||
|
||||
// Load implements configuration file loading. The supplied config file will be
|
||||
// read from disk (if specified) and overrides will be applied. If no config file is
|
||||
// specified, the overrides can still be applied to an empty config.
|
||||
func Load(configFile string, overrides []string, overrideFiles []string) ([]byte, error) {
|
||||
baseConf := map[string]any{}
|
||||
|
||||
// User specified config file
|
||||
if configFile != "" {
|
||||
var bytes []byte
|
||||
var err error
|
||||
bytes, err = os.ReadFile(configFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
processedConf := subEnvVars(string(bytes))
|
||||
|
||||
if err := yaml.Unmarshal([]byte(processedConf), &baseConf); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse %s: %s", configFile, err)
|
||||
}
|
||||
}
|
||||
|
||||
overrideConf := map[string]any{}
|
||||
|
||||
// User specified a config override via --set
|
||||
for _, override := range overrides {
|
||||
processedOverride := subEnvVars(override)
|
||||
if err := strvals.ParseInto(processedOverride, overrideConf); err != nil {
|
||||
return nil, fmt.Errorf("failed parsing --set data: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// User specified a config override value via --set-file
|
||||
for _, override := range overrideFiles {
|
||||
reader := func(rs []rune) (any, error) {
|
||||
bytes, err := os.ReadFile(string(rs))
|
||||
value := strings.TrimSpace(string(bytes))
|
||||
return value, err
|
||||
}
|
||||
if err := strvals.ParseIntoFile(override, overrideConf, reader); err != nil {
|
||||
return nil, fmt.Errorf("failed parsing --set-file data: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Merge together base config file and overrides, prefer the overrides
|
||||
conf := mergeValues(baseConf, overrideConf)
|
||||
|
||||
// Take the patched config and marshal back to YAML
|
||||
return yaml.Marshal(conf)
|
||||
}
|
||||
|
||||
// regex looking for ${...} notation strings
|
||||
var envRegex = regexp.MustCompile(`(?U:\${.*})`)
|
||||
|
||||
// SubEnvVars will look for any environment variables in the passed in string
|
||||
// with the syntax of ${VAR_NAME} and replace that string with ENV[VAR_NAME]
|
||||
func SubEnvVars(s string) string {
|
||||
return subEnvVars(s)
|
||||
}
|
||||
|
||||
func subEnvVars(s string) string {
|
||||
updatedConfig := envRegex.ReplaceAllStringFunc(s, func(s string) string {
|
||||
// Trim off the '${' and '}'
|
||||
if len(s) <= 3 {
|
||||
// This should never happen..
|
||||
return ""
|
||||
}
|
||||
varName := s[2 : len(s)-1]
|
||||
|
||||
// Lookup the variable in the environment. We do not
|
||||
// play by bash rules: if its undefined we'll keep it
|
||||
// as-is, it could be replaced somewhere down the line.
|
||||
// If it's set to "", we'll return that.
|
||||
if lu, ok := os.LookupEnv(varName); ok {
|
||||
return lu
|
||||
}
|
||||
return s
|
||||
})
|
||||
|
||||
return updatedConfig
|
||||
}
|
||||
|
||||
// mergeValues will merge source and destination map, preferring values from the source map
|
||||
func mergeValues(dest map[string]any, src map[string]any) map[string]any {
|
||||
for k, v := range src {
|
||||
// If the key doesn't exist already, then just set the key to that value
|
||||
if _, exists := dest[k]; !exists {
|
||||
dest[k] = v
|
||||
continue
|
||||
}
|
||||
nextMap, ok := v.(map[string]any)
|
||||
// If it isn't another map, overwrite the value
|
||||
if !ok {
|
||||
dest[k] = v
|
||||
continue
|
||||
}
|
||||
// Edge case: If the key exists in the destination, but isn't a map
|
||||
destMap, isMap := dest[k].(map[string]any)
|
||||
// If the source map has a map for this key, prefer it
|
||||
if !isMap {
|
||||
dest[k] = v
|
||||
continue
|
||||
}
|
||||
// If we got to this point, it is a map in both, so merge them
|
||||
dest[k] = mergeValues(destMap, nextMap)
|
||||
}
|
||||
return dest
|
||||
}
|
||||
+21
-4
@@ -2,10 +2,12 @@
|
||||
// which supports lookups, sets, appends, insertions, and deletions.
|
||||
package bitvector
|
||||
|
||||
import "slices"
|
||||
|
||||
// A BitVector is a variable sized vector of bits. It supports
|
||||
// lookups, sets, appends, insertions, and deletions.
|
||||
//
|
||||
// This class is not thread safe.
|
||||
// Operations are not thread safe.
|
||||
type BitVector struct {
|
||||
data []byte
|
||||
length int
|
||||
@@ -14,10 +16,25 @@ type BitVector struct {
|
||||
// NewBitVector creates and initializes a new bit vector with length
|
||||
// elements, using data as its initial contents.
|
||||
func NewBitVector(data []byte, length int) *BitVector {
|
||||
return &BitVector{
|
||||
data: data,
|
||||
length: length,
|
||||
return &BitVector{data: data, length: length}
|
||||
}
|
||||
|
||||
func (vector *BitVector) Clear() *BitVector {
|
||||
if vector == nil {
|
||||
return nil
|
||||
}
|
||||
clear(vector.data)
|
||||
vector.length = 0
|
||||
|
||||
return vector
|
||||
}
|
||||
|
||||
func (vector *BitVector) Reset(size, length int) *BitVector {
|
||||
clear(vector.data)
|
||||
vector.data = slices.Grow(vector.data, size)[:size]
|
||||
vector.length = length
|
||||
|
||||
return vector
|
||||
}
|
||||
|
||||
// Bytes returns a slice of the contents of the bit vector. If the caller changes the returned slice,
|
||||
|
||||
+163
-103
@@ -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,14 +639,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 := 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
|
||||
}
|
||||
return child.Unfold(path[1:])
|
||||
}
|
||||
return nil, fmt.Errorf("path %v does not exist in object term %v", ast.Ref{path[0]}, e.value.Value)
|
||||
return nil, fmt.Errorf("path %v does not exist in object term %v", path[0], e.value.Value)
|
||||
case ast.Set:
|
||||
// Sets' keys *are* their values, so in order to allow accurate
|
||||
// traversal, we have to collapse the tree beneath this node,
|
||||
@@ -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,10 +709,10 @@ 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.Ref{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)", ast.Ref{path[0]}, x, x)
|
||||
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) }
|
||||
|
||||
+53
-20
@@ -4,39 +4,72 @@ import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MustWriteTarGz write the list of file names and content
|
||||
// into a tarball.
|
||||
func MustWriteTarGz(files [][2]string) *bytes.Buffer {
|
||||
var buf bytes.Buffer
|
||||
gw := gzip.NewWriter(&buf)
|
||||
defer gw.Close()
|
||||
tw := tar.NewWriter(gw)
|
||||
defer tw.Close()
|
||||
for _, file := range files {
|
||||
if err := WriteFile(tw, file[0], []byte(file[1])); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return &buf
|
||||
type TarGzWriter struct {
|
||||
*tar.Writer
|
||||
|
||||
gw *gzip.Writer
|
||||
}
|
||||
|
||||
// WriteFile adds a file header with content to the given tar writer
|
||||
func WriteFile(tw *tar.Writer, path string, bs []byte) error {
|
||||
func NewTarGzWriter(w io.Writer) *TarGzWriter {
|
||||
gw := gzip.NewWriter(w)
|
||||
tw := tar.NewWriter(gw)
|
||||
|
||||
return &TarGzWriter{
|
||||
Writer: tw,
|
||||
gw: gw,
|
||||
}
|
||||
}
|
||||
|
||||
func (tgw *TarGzWriter) WriteFile(path string, bs []byte) (err error) {
|
||||
hdr := &tar.Header{
|
||||
Name: "/" + strings.TrimLeft(path, "/"),
|
||||
Name: path,
|
||||
Mode: 0600,
|
||||
Typeflag: tar.TypeReg,
|
||||
Size: int64(len(bs)),
|
||||
}
|
||||
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
if err = tgw.WriteHeader(hdr); err == nil {
|
||||
_, err = tgw.Write(bs)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (tgw *TarGzWriter) WriteJSONFile(path string, v any) error {
|
||||
buf := &bytes.Buffer{}
|
||||
if err := json.NewEncoder(buf).Encode(v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := tw.Write(bs)
|
||||
return err
|
||||
return tgw.WriteFile(path, buf.Bytes())
|
||||
}
|
||||
|
||||
func (tgw *TarGzWriter) Close() error {
|
||||
return errors.Join(tgw.Writer.Close(), tgw.gw.Close())
|
||||
}
|
||||
|
||||
// MustWriteTarGz writes the list of file names and content into a tarball.
|
||||
// Paths are prefixed with "/".
|
||||
func MustWriteTarGz(files [][2]string) *bytes.Buffer {
|
||||
buf := &bytes.Buffer{}
|
||||
tgw := NewTarGzWriter(buf)
|
||||
defer tgw.Close()
|
||||
|
||||
for _, file := range files {
|
||||
if !strings.HasPrefix(file[0], "/") {
|
||||
file[0] = "/" + file[0]
|
||||
}
|
||||
|
||||
if err := tgw.WriteFile(file[0], []byte(file[1])); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@
|
||||
//
|
||||
// created 26-02-2013
|
||||
|
||||
// nolint: deadcode,unused,varcheck // Package in development (2021).
|
||||
// nolint:unused,varcheck // Package in development (2021).
|
||||
package gojsonschema
|
||||
|
||||
import (
|
||||
|
||||
+2
-2
@@ -1768,7 +1768,7 @@ func (p *Planner) planRef(ref ast.Ref, iter planiter) error {
|
||||
return errors.New("illegal ref: non-var head")
|
||||
}
|
||||
|
||||
if head.Compare(ast.DefaultRootDocument.Value) == 0 {
|
||||
if head.Equal(ast.DefaultRootDocument.Value) {
|
||||
virtual := p.rules.Get(ref[0].Value)
|
||||
base := &baseptr{local: p.vars.GetOrEmpty(ast.DefaultRootDocument.Value.(ast.Var))}
|
||||
return p.planRefData(virtual, base, ref, 1, iter)
|
||||
@@ -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()...)
|
||||
|
||||
+2
@@ -158,6 +158,8 @@ func SignV4(headers map[string][]string, method string, theURL *url.URL, body []
|
||||
// include the values for the signed headers
|
||||
orderedKeys := util.KeysSorted(headersToSign)
|
||||
for _, k := range orderedKeys {
|
||||
// TODO: fix later
|
||||
//nolint:perfsprint
|
||||
canonicalReq += k + ":" + strings.Join(headersToSign[k], ",") + "\n"
|
||||
}
|
||||
canonicalReq += "\n" // linefeed to terminate headers
|
||||
|
||||
+2
-2
@@ -7,16 +7,16 @@ package ref
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/ast"
|
||||
"github.com/open-policy-agent/opa/v1/storage"
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// ParseDataPath returns a ref from the slash separated path s rooted at data.
|
||||
// All path segments are treated as identifier strings.
|
||||
func ParseDataPath(s string) (ast.Ref, error) {
|
||||
path, ok := storage.ParsePath("/" + strings.TrimPrefix(s, "/"))
|
||||
path, ok := storage.ParsePath(util.WithPrefix(s, "/"))
|
||||
if !ok {
|
||||
return nil, errors.New("invalid path")
|
||||
}
|
||||
|
||||
-218
@@ -1,218 +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 report provides functions to report OPA's version information to an external service and process the response.
|
||||
package report
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"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/report.ExternalServiceURL=<url>"
|
||||
// -ldflags "-X github.com/open-policy-agent/opa/internal/report.GHRepo=<url>"
|
||||
//
|
||||
// ExternalServiceURL will be overridden if the OPA_TELEMETRY_SERVICE_URL environment variable
|
||||
// is provided.
|
||||
var ExternalServiceURL = "https://api.github.com"
|
||||
var GHRepo = "open-policy-agent/opa"
|
||||
|
||||
// Reporter reports information such as the version, heap usage about the running OPA instance to an external service
|
||||
type Reporter interface {
|
||||
SendReport(ctx context.Context) (*DataResponse, error)
|
||||
RegisterGatherer(key string, f Gatherer)
|
||||
}
|
||||
|
||||
// Gatherer represents a mechanism to inject additional data in the telemetry report
|
||||
type Gatherer func(ctx context.Context) (any, error)
|
||||
|
||||
// DataResponse represents the data returned by the external service
|
||||
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 reporter.
|
||||
type Options struct {
|
||||
Logger logging.Logger
|
||||
}
|
||||
|
||||
type GHVersionCollector struct {
|
||||
client rest.Client
|
||||
}
|
||||
|
||||
type GHResponse 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 Reporter
|
||||
func New(opts Options) (Reporter, error) {
|
||||
r := GHVersionCollector{}
|
||||
|
||||
url := cmp.Or(os.Getenv("OPA_TELEMETRY_SERVICE_URL"), ExternalServiceURL)
|
||||
|
||||
restConfig := fmt.Appendf(nil, `{
|
||||
"url": %q,
|
||||
}`, url)
|
||||
|
||||
client, err := rest.New(restConfig, map[string]*keys.Config{}, rest.Logger(opts.Logger))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.client = client
|
||||
|
||||
// heap_usage_bytes is always present, so register it unconditionally
|
||||
r.RegisterGatherer("heap_usage_bytes", readRuntimeMemStats)
|
||||
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
// SendReport sends the telemetry report which includes information such as the OPA version, current memory usage to
|
||||
// the external service
|
||||
func (r *GHVersionCollector) SendReport(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 GHResponse
|
||||
err := json.NewDecoder(resp.Body).Decode(&result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return createDataResponse(result)
|
||||
}
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("server replied with HTTP %v", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func createDataResponse(ghResp GHResponse) (*DataResponse, error) {
|
||||
if ghResp.TagName == "" {
|
||||
return nil, errors.New("server response does not contain tag_name")
|
||||
}
|
||||
|
||||
v := strings.TrimPrefix(version.Version, "v")
|
||||
sv, err := semver.NewVersion(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse current version %q: %w", v, err)
|
||||
}
|
||||
|
||||
latestV := strings.TrimPrefix(ghResp.TagName, "v")
|
||||
latestSV, err := semver.NewVersion(latestV)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse latest version %q: %w", latestV, 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 (*GHVersionCollector) 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")
|
||||
}
|
||||
|
||||
func readRuntimeMemStats(_ context.Context) (any, error) {
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
return strconv.FormatUint(m.Alloc, 10), nil
|
||||
}
|
||||
-261
@@ -1,261 +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"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(cleanedPath, "/") {
|
||||
cleanedPath = "/" + cleanedPath
|
||||
}
|
||||
|
||||
result.FileDescriptors = append(result.FileDescriptors, &Descriptor{
|
||||
Root: path,
|
||||
Path: cleanedPath,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
+194
-197
@@ -14,237 +14,234 @@
|
||||
|
||||
// Semantic Versions http://semver.org
|
||||
|
||||
// Package semver has been vendored from:
|
||||
// This file was originally vendored from:
|
||||
// https://github.com/coreos/go-semver/tree/e214231b295a8ea9479f11b70b35d5acf3556d9b/semver
|
||||
// A number of the original functions of the package have been removed since
|
||||
// they are not required for our built-ins.
|
||||
// There isn't a single line left from the original source today, but being generous about
|
||||
// attribution won't hurt.
|
||||
package semver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// reMetaIdentifier matches pre-release and metadata identifiers against the spec requirements
|
||||
var reMetaIdentifier = regexp.MustCompile(`^[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*$`)
|
||||
|
||||
// Version represents a parsed SemVer
|
||||
type Version struct {
|
||||
Major int64
|
||||
Minor int64
|
||||
Patch int64
|
||||
PreRelease PreRelease
|
||||
Metadata string
|
||||
PreRelease string `json:"PreRelease,omitempty"`
|
||||
Metadata string `json:"Metadata,omitempty"`
|
||||
}
|
||||
|
||||
// PreRelease represents a pre-release suffix string
|
||||
type PreRelease string
|
||||
// Parse constructs new semver Version from version string.
|
||||
func Parse(version string) (v Version, err error) {
|
||||
version = strings.TrimPrefix(version, "v")
|
||||
|
||||
func splitOff(input *string, delim string) (val string) {
|
||||
parts := strings.SplitN(*input, delim, 2)
|
||||
|
||||
if len(parts) == 2 {
|
||||
*input = parts[0]
|
||||
val = parts[1]
|
||||
version, v.Metadata = cut(version, '+')
|
||||
if v.Metadata != "" && !reMetaIdentifier.MatchString(v.Metadata) {
|
||||
return v, fmt.Errorf("invalid metadata identifier: %s", v.Metadata)
|
||||
}
|
||||
|
||||
return val
|
||||
version, v.PreRelease = cut(version, '-')
|
||||
if v.PreRelease != "" && !reMetaIdentifier.MatchString(v.PreRelease) {
|
||||
return v, fmt.Errorf("invalid pre-release identifier: %s", v.PreRelease)
|
||||
}
|
||||
|
||||
if strings.Count(version, ".") != 2 {
|
||||
return v, fmt.Errorf("%s should contain major, minor, and patch versions", version)
|
||||
}
|
||||
|
||||
major, after := cut(version, '.')
|
||||
if v.Major, err = strconv.ParseInt(major, 10, 64); err != nil {
|
||||
return v, err
|
||||
}
|
||||
|
||||
minor, after := cut(after, '.')
|
||||
if v.Minor, err = strconv.ParseInt(minor, 10, 64); err != nil {
|
||||
return v, err
|
||||
}
|
||||
|
||||
if v.Patch, err = strconv.ParseInt(after, 10, 64); err != nil {
|
||||
return v, err
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// NewVersion constructs new SemVers from strings
|
||||
func NewVersion(version string) (*Version, error) {
|
||||
v := Version{}
|
||||
|
||||
if err := v.Set(version); err != nil {
|
||||
return nil, err
|
||||
// MustParse is like Parse but panics if the version string is invalid instead of returning an error.
|
||||
func MustParse(version string) Version {
|
||||
v, err := Parse(version)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &v, nil
|
||||
return v
|
||||
}
|
||||
|
||||
// Set parses and updates v from the given version string. Implements flag.Value
|
||||
func (v *Version) Set(version string) error {
|
||||
metadata := splitOff(&version, "+")
|
||||
preRelease := PreRelease(splitOff(&version, "-"))
|
||||
dotParts := strings.SplitN(version, ".", 3)
|
||||
|
||||
if len(dotParts) != 3 {
|
||||
return fmt.Errorf("%s is not in dotted-tri format", version)
|
||||
}
|
||||
|
||||
if err := validateIdentifier(string(preRelease)); err != nil {
|
||||
return fmt.Errorf("failed to validate pre-release: %v", err)
|
||||
}
|
||||
|
||||
if err := validateIdentifier(metadata); err != nil {
|
||||
return fmt.Errorf("failed to validate metadata: %v", err)
|
||||
}
|
||||
|
||||
parsed := make([]int64, 3)
|
||||
|
||||
for i, v := range dotParts[:3] {
|
||||
val, err := strconv.ParseInt(v, 10, 64)
|
||||
parsed[i] = val
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
v.Metadata = metadata
|
||||
v.PreRelease = preRelease
|
||||
v.Major = parsed[0]
|
||||
v.Minor = parsed[1]
|
||||
v.Patch = parsed[2]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v Version) String() string {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
fmt.Fprintf(&buffer, "%d.%d.%d", v.Major, v.Minor, v.Patch)
|
||||
|
||||
if v.PreRelease != "" {
|
||||
fmt.Fprintf(&buffer, "-%s", v.PreRelease)
|
||||
}
|
||||
|
||||
if v.Metadata != "" {
|
||||
fmt.Fprintf(&buffer, "+%s", v.Metadata)
|
||||
}
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// Compare tests if v is less than, equal to, or greater than versionB,
|
||||
// returning -1, 0, or +1 respectively.
|
||||
func (v Version) Compare(versionB Version) int {
|
||||
if cmp := recursiveCompare(v.Slice(), versionB.Slice()); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
return preReleaseCompare(v, versionB)
|
||||
}
|
||||
|
||||
// Slice converts the comparable parts of the semver into a slice of integers.
|
||||
func (v Version) Slice() []int64 {
|
||||
return []int64{v.Major, v.Minor, v.Patch}
|
||||
}
|
||||
|
||||
// Slice splits the pre-release suffix string
|
||||
func (p PreRelease) Slice() []string {
|
||||
preRelease := string(p)
|
||||
return strings.Split(preRelease, ".")
|
||||
}
|
||||
|
||||
func preReleaseCompare(versionA Version, versionB Version) int {
|
||||
a := versionA.PreRelease
|
||||
b := versionB.PreRelease
|
||||
|
||||
/* Handle the case where if two versions are otherwise equal it is the
|
||||
* one without a PreRelease that is greater */
|
||||
if len(a) == 0 && (len(b) > 0) {
|
||||
return 1
|
||||
} else if len(b) == 0 && (len(a) > 0) {
|
||||
return -1
|
||||
}
|
||||
|
||||
// If there is a prerelease, check and compare each part.
|
||||
return recursivePreReleaseCompare(a.Slice(), b.Slice())
|
||||
}
|
||||
|
||||
func recursiveCompare(versionA []int64, versionB []int64) int {
|
||||
if len(versionA) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
a := versionA[0]
|
||||
b := versionB[0]
|
||||
|
||||
if a > b {
|
||||
return 1
|
||||
} else if a < b {
|
||||
return -1
|
||||
}
|
||||
|
||||
return recursiveCompare(versionA[1:], versionB[1:])
|
||||
}
|
||||
|
||||
func recursivePreReleaseCompare(versionA []string, versionB []string) int {
|
||||
// A larger set of pre-release fields has a higher precedence than a smaller set,
|
||||
// if all of the preceding identifiers are equal.
|
||||
if len(versionA) == 0 {
|
||||
if len(versionB) > 0 {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
} else if len(versionB) == 0 {
|
||||
// We're longer than versionB so return 1.
|
||||
return 1
|
||||
}
|
||||
|
||||
a := versionA[0]
|
||||
b := versionB[0]
|
||||
|
||||
aInt := false
|
||||
bInt := false
|
||||
|
||||
aI, err := strconv.Atoi(versionA[0])
|
||||
if err == nil {
|
||||
aInt = true
|
||||
}
|
||||
|
||||
bI, err := strconv.Atoi(versionB[0])
|
||||
if err == nil {
|
||||
bInt = true
|
||||
}
|
||||
|
||||
// Numeric identifiers always have lower precedence than non-numeric identifiers.
|
||||
if aInt && !bInt {
|
||||
return -1
|
||||
} else if !aInt && bInt {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Handle Integer Comparison
|
||||
if aInt && bInt {
|
||||
if aI > bI {
|
||||
return 1
|
||||
} else if aI < bI {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// Handle String Comparison
|
||||
if a > b {
|
||||
return 1
|
||||
} else if a < b {
|
||||
return -1
|
||||
}
|
||||
|
||||
return recursivePreReleaseCompare(versionA[1:], versionB[1:])
|
||||
}
|
||||
|
||||
// validateIdentifier makes sure the provided identifier satisfies semver spec
|
||||
func validateIdentifier(id string) error {
|
||||
if id != "" && !reIdentifier.MatchString(id) {
|
||||
return fmt.Errorf("%s is not a valid semver identifier", id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// reIdentifier is a regular expression used to check that pre-release and metadata
|
||||
// identifiers satisfy the spec requirements
|
||||
var reIdentifier = regexp.MustCompile(`^[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*$`)
|
||||
|
||||
// Compare compares two semver strings.
|
||||
func Compare(a, b string) int {
|
||||
aV, err := NewVersion(strings.TrimPrefix(a, "v"))
|
||||
aV, err := Parse(a)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
bV, err := NewVersion(strings.TrimPrefix(b, "v"))
|
||||
bV, err := Parse(b)
|
||||
if err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
return aV.Compare(*bV)
|
||||
return aV.Compare(bV)
|
||||
}
|
||||
|
||||
// AppendText appends the textual representation of the version to b and returns the extended buffer.
|
||||
// This method conforms to the encoding.TextAppender interface, and is useful for serializing the Version
|
||||
// without allocating, provided the caller has pre-allocated sufficient space in b.
|
||||
func (v Version) AppendText(b []byte) ([]byte, error) {
|
||||
if b == nil {
|
||||
b = make([]byte, 0, length(v))
|
||||
}
|
||||
|
||||
b = append(strconv.AppendInt(b, v.Major, 10), '.')
|
||||
b = append(strconv.AppendInt(b, v.Minor, 10), '.')
|
||||
b = strconv.AppendInt(b, v.Patch, 10)
|
||||
|
||||
if v.PreRelease != "" {
|
||||
b = append(append(b, '-'), v.PreRelease...)
|
||||
}
|
||||
if v.Metadata != "" {
|
||||
b = append(append(b, '+'), v.Metadata...)
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// String returns the string representation of the version.
|
||||
func (v Version) String() string {
|
||||
bs := make([]byte, 0, length(v))
|
||||
bs, _ = v.AppendText(bs)
|
||||
|
||||
return string(bs)
|
||||
}
|
||||
|
||||
// Compare tests if v is less than, equal to, or greater than other, returning -1, 0, or +1 respectively.
|
||||
// Comparison is based on the SemVer specification (https://semver.org/#spec-item-11).
|
||||
func (v Version) Compare(other Version) int {
|
||||
if v.Major > other.Major {
|
||||
return 1
|
||||
} else if v.Major < other.Major {
|
||||
return -1
|
||||
}
|
||||
|
||||
if v.Minor > other.Minor {
|
||||
return 1
|
||||
} else if v.Minor < other.Minor {
|
||||
return -1
|
||||
}
|
||||
|
||||
if v.Patch > other.Patch {
|
||||
return 1
|
||||
} else if v.Patch < other.Patch {
|
||||
return -1
|
||||
}
|
||||
|
||||
if v.PreRelease == other.PreRelease {
|
||||
return 0
|
||||
}
|
||||
|
||||
// if two versions are otherwise equal it is the one without a pre-release that is greater
|
||||
if v.PreRelease == "" && other.PreRelease != "" {
|
||||
return 1
|
||||
}
|
||||
if other.PreRelease == "" && v.PreRelease != "" {
|
||||
return -1
|
||||
}
|
||||
|
||||
a, afterA := cut(v.PreRelease, '.')
|
||||
b, afterB := cut(other.PreRelease, '.')
|
||||
|
||||
for {
|
||||
if a == "" && b != "" {
|
||||
return -1
|
||||
}
|
||||
if a != "" && b == "" {
|
||||
return 1
|
||||
}
|
||||
|
||||
aIsInt := isAllDecimals(a)
|
||||
bIsInt := isAllDecimals(b)
|
||||
|
||||
// numeric identifiers have lower precedence than non-numeric
|
||||
if aIsInt && !bIsInt {
|
||||
return -1
|
||||
} else if !aIsInt && bIsInt {
|
||||
return 1
|
||||
}
|
||||
|
||||
if aIsInt && bIsInt {
|
||||
aInt, _ := strconv.Atoi(a)
|
||||
bInt, _ := strconv.Atoi(b)
|
||||
|
||||
if aInt > bInt {
|
||||
return 1
|
||||
} else if aInt < bInt {
|
||||
return -1
|
||||
}
|
||||
} else {
|
||||
// string comparison
|
||||
if a > b {
|
||||
return 1
|
||||
} else if a < b {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// a larger set of pre-release fields has a higher precedence than a
|
||||
// smaller set, if all of the preceding identifiers are equal.
|
||||
if afterA != "" && afterB == "" {
|
||||
return 1
|
||||
} else if afterA == "" && afterB != "" {
|
||||
return -1
|
||||
}
|
||||
|
||||
a, afterA = cut(afterA, '.')
|
||||
b, afterB = cut(afterB, '.')
|
||||
}
|
||||
}
|
||||
|
||||
func isAllDecimals(s string) bool {
|
||||
for _, r := range s {
|
||||
if r < '0' || r > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return s != ""
|
||||
}
|
||||
|
||||
// length allows calculating the length of the version for pre-allocation.
|
||||
func length(v Version) int {
|
||||
n := util.NumDigitsInt64(v.Major) + util.NumDigitsInt64(v.Minor) + util.NumDigitsInt64(v.Patch) + 2
|
||||
if v.PreRelease != "" {
|
||||
n += len(v.PreRelease) + 1
|
||||
}
|
||||
if v.Metadata != "" {
|
||||
n += len(v.Metadata) + 1
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// cut is a *slightly* faster version of strings.Cut only accepting
|
||||
// single byte separators, and skipping the boolean return value.
|
||||
func cut(s string, sep byte) (before, after string) {
|
||||
if i := strings.IndexByte(s, sep); i >= 0 {
|
||||
return s[:i], s[i+1:]
|
||||
}
|
||||
return s, ""
|
||||
}
|
||||
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
Package strvals provides tools for working with strval lines.
|
||||
|
||||
OPA runtime config supports a compressed format for YAML settings which we call strvals.
|
||||
The format is roughly like this:
|
||||
|
||||
name=value,topname.subname=value
|
||||
|
||||
The above is equivalent to the YAML document
|
||||
|
||||
name: value
|
||||
topname:
|
||||
subname: value
|
||||
|
||||
This package provides a parser and utilities for converting the strvals format
|
||||
to other formats.
|
||||
*/
|
||||
package strvals
|
||||
-429
@@ -1,429 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package strvals
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// ErrNotList indicates that a non-list was treated as a list.
|
||||
var ErrNotList = errors.New("not a list")
|
||||
|
||||
// MaxIndex is the maximum index that will be allowed by setIndex.
|
||||
// The default value 65536 = 1024 * 64
|
||||
const MaxIndex = 65536
|
||||
|
||||
// ToYAML takes a string of arguments and converts to a YAML document.
|
||||
func ToYAML(s string) (string, error) {
|
||||
m, err := Parse(s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
d, err := yaml.Marshal(m)
|
||||
return string(d), err
|
||||
}
|
||||
|
||||
// Parse parses a set line.
|
||||
//
|
||||
// A set line is of the form name1=value1,name2=value2
|
||||
func Parse(s string) (map[string]any, error) {
|
||||
vals := map[string]any{}
|
||||
scanner := bytes.NewBufferString(s)
|
||||
t := newParser(scanner, vals, false)
|
||||
err := t.parse()
|
||||
return vals, err
|
||||
}
|
||||
|
||||
// ParseString parses a set line and forces a string value.
|
||||
//
|
||||
// A set line is of the form name1=value1,name2=value2
|
||||
func ParseString(s string) (map[string]any, error) {
|
||||
vals := map[string]any{}
|
||||
scanner := bytes.NewBufferString(s)
|
||||
t := newParser(scanner, vals, true)
|
||||
err := t.parse()
|
||||
return vals, err
|
||||
}
|
||||
|
||||
// ParseInto parses a strvals line and merges the result into dest.
|
||||
//
|
||||
// If the strval string has a key that exists in dest, it overwrites the
|
||||
// dest version.
|
||||
func ParseInto(s string, dest map[string]any) error {
|
||||
scanner := bytes.NewBufferString(s)
|
||||
t := newParser(scanner, dest, false)
|
||||
return t.parse()
|
||||
}
|
||||
|
||||
// ParseIntoFile parses a filevals line and merges the result into dest.
|
||||
//
|
||||
// This method always returns a string as the value.
|
||||
func ParseIntoFile(s string, dest map[string]any, runesToVal runesToVal) error {
|
||||
scanner := bytes.NewBufferString(s)
|
||||
t := newFileParser(scanner, dest, runesToVal)
|
||||
return t.parse()
|
||||
}
|
||||
|
||||
// ParseIntoString parses a strvals line and merges the result into dest.
|
||||
//
|
||||
// This method always returns a string as the value.
|
||||
func ParseIntoString(s string, dest map[string]any) error {
|
||||
scanner := bytes.NewBufferString(s)
|
||||
t := newParser(scanner, dest, true)
|
||||
return t.parse()
|
||||
}
|
||||
|
||||
// parser is a simple parser that takes a strvals line and parses it into a
|
||||
// map representation.
|
||||
//
|
||||
// where sc is the source of the original data being parsed
|
||||
// where data is the final parsed data from the parses with correct types
|
||||
// where st is a boolean to figure out if we're forcing it to parse values as string
|
||||
type parser struct {
|
||||
sc *bytes.Buffer
|
||||
data map[string]any
|
||||
runesToVal runesToVal
|
||||
}
|
||||
|
||||
type runesToVal func([]rune) (any, error)
|
||||
|
||||
func newParser(sc *bytes.Buffer, data map[string]any, stringBool bool) *parser {
|
||||
rs2v := func(rs []rune) (any, error) {
|
||||
return typedVal(rs, stringBool), nil
|
||||
}
|
||||
return &parser{sc: sc, data: data, runesToVal: rs2v}
|
||||
}
|
||||
|
||||
func newFileParser(sc *bytes.Buffer, data map[string]any, runesToVal runesToVal) *parser {
|
||||
return &parser{sc: sc, data: data, runesToVal: runesToVal}
|
||||
}
|
||||
|
||||
func (t *parser) parse() error {
|
||||
for {
|
||||
err := t.key(t.data)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func runeSet(r []rune) map[rune]bool {
|
||||
s := make(map[rune]bool, len(r))
|
||||
for _, rr := range r {
|
||||
s[rr] = true
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (t *parser) key(data map[string]any) error {
|
||||
stop := runeSet([]rune{'=', '[', ',', '.'})
|
||||
for {
|
||||
switch k, last, err := runesUntil(t.sc, stop); {
|
||||
case err != nil:
|
||||
if len(k) == 0 {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("key %q has no value", string(k))
|
||||
case last == '[':
|
||||
// We are in a list index context, so we need to set an index.
|
||||
i, err := t.keyIndex()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing index: %s", err)
|
||||
}
|
||||
kk := string(k)
|
||||
// Find or create target list
|
||||
list := []any{}
|
||||
if _, ok := data[kk]; ok {
|
||||
list = data[kk].([]any)
|
||||
}
|
||||
|
||||
// Now we need to get the value after the ].
|
||||
list, err = t.listItem(list, i)
|
||||
set(data, kk, list)
|
||||
return err
|
||||
case last == '=':
|
||||
// End of key. Consume =, Get value.
|
||||
// FIXME: Get value list first
|
||||
vl, e := t.valList()
|
||||
switch e {
|
||||
case nil:
|
||||
set(data, string(k), vl)
|
||||
return nil
|
||||
case io.EOF:
|
||||
set(data, string(k), "")
|
||||
return e
|
||||
case ErrNotList:
|
||||
rs, e := t.val()
|
||||
if e != nil && e != io.EOF {
|
||||
return e
|
||||
}
|
||||
v, e := t.runesToVal(rs)
|
||||
set(data, string(k), v)
|
||||
return e
|
||||
default:
|
||||
return e
|
||||
}
|
||||
|
||||
case last == ',':
|
||||
// No value given. Set the value to empty string. Return error.
|
||||
set(data, string(k), "")
|
||||
return fmt.Errorf("key %q has no value (cannot end with ,)", string(k))
|
||||
case last == '.':
|
||||
// First, create or find the target map.
|
||||
inner := map[string]any{}
|
||||
if _, ok := data[string(k)]; ok {
|
||||
inner = data[string(k)].(map[string]any)
|
||||
}
|
||||
|
||||
// Recurse
|
||||
e := t.key(inner)
|
||||
if len(inner) == 0 {
|
||||
return fmt.Errorf("key map %q has no value", string(k))
|
||||
}
|
||||
set(data, string(k), inner)
|
||||
return e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func set(data map[string]any, key string, val any) {
|
||||
// If key is empty, don't set it.
|
||||
if len(key) == 0 {
|
||||
return
|
||||
}
|
||||
data[key] = val
|
||||
}
|
||||
|
||||
func setIndex(list []any, index int, val any) (l2 []any, err error) {
|
||||
// There are possible index values that are out of range on a target system
|
||||
// causing a panic. This will catch the panic and return an error instead.
|
||||
// The value of the index that causes a panic varies from system to system.
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("error processing index %d: %s", index, r)
|
||||
}
|
||||
}()
|
||||
|
||||
if index < 0 {
|
||||
return list, fmt.Errorf("negative %d index not allowed", index)
|
||||
}
|
||||
if index > MaxIndex {
|
||||
return list, fmt.Errorf("index of %d is greater than maximum supported index of %d", index, MaxIndex)
|
||||
}
|
||||
if len(list) <= index {
|
||||
newlist := make([]any, index+1)
|
||||
copy(newlist, list)
|
||||
list = newlist
|
||||
}
|
||||
list[index] = val
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (t *parser) keyIndex() (int, error) {
|
||||
// First, get the key.
|
||||
stop := runeSet([]rune{']'})
|
||||
v, _, err := runesUntil(t.sc, stop)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// v should be the index
|
||||
return strconv.Atoi(string(v))
|
||||
|
||||
}
|
||||
func (t *parser) listItem(list []any, i int) ([]any, error) {
|
||||
if i < 0 {
|
||||
return list, fmt.Errorf("negative %d index not allowed", i)
|
||||
}
|
||||
stop := runeSet([]rune{'[', '.', '='})
|
||||
switch k, last, err := runesUntil(t.sc, stop); {
|
||||
case len(k) > 0:
|
||||
return list, fmt.Errorf("unexpected data at end of array index: %q", k)
|
||||
case err != nil:
|
||||
return list, err
|
||||
case last == '=':
|
||||
vl, e := t.valList()
|
||||
switch e {
|
||||
case nil:
|
||||
return setIndex(list, i, vl)
|
||||
case io.EOF:
|
||||
return setIndex(list, i, "")
|
||||
case ErrNotList:
|
||||
rs, e := t.val()
|
||||
if e != nil && e != io.EOF {
|
||||
return list, e
|
||||
}
|
||||
v, e := t.runesToVal(rs)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
return setIndex(list, i, v)
|
||||
default:
|
||||
return list, e
|
||||
}
|
||||
case last == '[':
|
||||
// now we have a nested list. Read the index and handle.
|
||||
i, err := t.keyIndex()
|
||||
if err != nil {
|
||||
return list, fmt.Errorf("error parsing index: %s", err)
|
||||
}
|
||||
// Now we need to get the value after the ].
|
||||
list2, err := t.listItem(list, i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return setIndex(list, i, list2)
|
||||
case last == '.':
|
||||
// We have a nested object. Send to t.key
|
||||
inner := map[string]any{}
|
||||
if len(list) > i {
|
||||
var ok bool
|
||||
inner, ok = list[i].(map[string]any)
|
||||
if !ok {
|
||||
// We have indices out of order. Initialize empty value.
|
||||
list[i] = map[string]any{}
|
||||
inner = list[i].(map[string]any)
|
||||
}
|
||||
}
|
||||
|
||||
// Recurse
|
||||
e := t.key(inner)
|
||||
if e != nil {
|
||||
return list, e
|
||||
}
|
||||
return setIndex(list, i, inner)
|
||||
default:
|
||||
return nil, fmt.Errorf("parse error: unexpected token %v", last)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *parser) val() ([]rune, error) {
|
||||
stop := runeSet([]rune{','})
|
||||
v, _, err := runesUntil(t.sc, stop)
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (t *parser) valList() ([]any, error) {
|
||||
r, _, e := t.sc.ReadRune()
|
||||
if e != nil {
|
||||
return []any{}, e
|
||||
}
|
||||
|
||||
if r != '{' {
|
||||
e = t.sc.UnreadRune()
|
||||
if e != nil {
|
||||
return []any{}, e
|
||||
}
|
||||
return []any{}, ErrNotList
|
||||
}
|
||||
|
||||
list := []any{}
|
||||
stop := runeSet([]rune{',', '}'})
|
||||
for {
|
||||
switch rs, last, err := runesUntil(t.sc, stop); {
|
||||
case err != nil:
|
||||
if err == io.EOF {
|
||||
err = errors.New("list must terminate with '}'")
|
||||
}
|
||||
return list, err
|
||||
case last == '}':
|
||||
// If this is followed by ',', consume it.
|
||||
if r, _, e := t.sc.ReadRune(); e == nil && r != ',' {
|
||||
e = t.sc.UnreadRune()
|
||||
if e != nil {
|
||||
return []any{}, e
|
||||
}
|
||||
}
|
||||
v, e := t.runesToVal(rs)
|
||||
list = append(list, v)
|
||||
return list, e
|
||||
case last == ',':
|
||||
v, e := t.runesToVal(rs)
|
||||
if e != nil {
|
||||
return list, e
|
||||
}
|
||||
list = append(list, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runesUntil(in io.RuneReader, stop map[rune]bool) ([]rune, rune, error) {
|
||||
var v []rune
|
||||
for {
|
||||
switch r, _, e := in.ReadRune(); {
|
||||
case e != nil:
|
||||
return v, r, e
|
||||
case inMap(r, stop):
|
||||
return v, r, nil
|
||||
case r == '\\':
|
||||
next, _, e := in.ReadRune()
|
||||
if e != nil {
|
||||
return v, next, e
|
||||
}
|
||||
v = append(v, next)
|
||||
default:
|
||||
v = append(v, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func inMap(k rune, m map[rune]bool) bool {
|
||||
_, ok := m[k]
|
||||
return ok
|
||||
}
|
||||
|
||||
func typedVal(v []rune, st bool) any {
|
||||
val := string(v)
|
||||
|
||||
if st {
|
||||
return val
|
||||
}
|
||||
|
||||
if strings.EqualFold(val, "true") {
|
||||
return true
|
||||
}
|
||||
|
||||
if strings.EqualFold(val, "false") {
|
||||
return false
|
||||
}
|
||||
|
||||
if strings.EqualFold(val, "null") {
|
||||
return struct{}{}
|
||||
}
|
||||
|
||||
if strings.EqualFold(val, "0") {
|
||||
return int64(0)
|
||||
}
|
||||
|
||||
// If this value does not start with zero, try parsing it to an int
|
||||
if len(val) != 0 && val[0] != '0' {
|
||||
if iv, err := strconv.ParseInt(val, 10, 64); err == nil {
|
||||
return iv
|
||||
}
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
+1
-1
@@ -83,7 +83,7 @@ func readModule(r io.Reader) (*module.Module, error) {
|
||||
|
||||
var m module.Module
|
||||
|
||||
if err := readSections(r, &m); err != nil && err != io.EOF {
|
||||
if err := readSections(r, &m); err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
+2
-16
@@ -433,18 +433,7 @@ func (a *Annotations) toObject() (*Object, *Error) {
|
||||
}
|
||||
|
||||
if len(a.Scope) > 0 {
|
||||
switch a.Scope {
|
||||
case annotationScopeDocument:
|
||||
obj.Insert(InternedTerm("scope"), InternedTerm("document"))
|
||||
case annotationScopePackage:
|
||||
obj.Insert(InternedTerm("scope"), InternedTerm("package"))
|
||||
case annotationScopeRule:
|
||||
obj.Insert(InternedTerm("scope"), InternedTerm("rule"))
|
||||
case annotationScopeSubpackages:
|
||||
obj.Insert(InternedTerm("scope"), InternedTerm("subpackages"))
|
||||
default:
|
||||
obj.Insert(InternedTerm("scope"), StringTerm(a.Scope))
|
||||
}
|
||||
obj.Insert(InternedTerm("scope"), InternedTerm(a.Scope))
|
||||
}
|
||||
|
||||
if len(a.Title) > 0 {
|
||||
@@ -752,10 +741,7 @@ func (c *CompileAnnotation) Compare(other *CompileAnnotation) int {
|
||||
return -1
|
||||
}
|
||||
|
||||
if cmp := slices.CompareFunc(c.Unknowns, other.Unknowns,
|
||||
func(x, y Ref) int {
|
||||
return x.Compare(y)
|
||||
}); cmp != 0 {
|
||||
if cmp := slices.CompareFunc(c.Unknowns, other.Unknowns, RefCompare); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
return c.MaskRule.Compare(other.MaskRule)
|
||||
|
||||
+68
-42
@@ -26,11 +26,16 @@ func RegisterBuiltin(b *Builtin) {
|
||||
BuiltinMap[b.Infix] = b
|
||||
|
||||
InternStringTerm(b.Infix)
|
||||
InternVarValue(b.Infix)
|
||||
}
|
||||
|
||||
InternStringTerm(b.Name)
|
||||
if strings.Contains(b.Name, ".") {
|
||||
InternStringTerm(strings.Split(b.Name, ".")...)
|
||||
parts := strings.Split(b.Name, ".")
|
||||
InternStringTerm(parts...)
|
||||
InternVarValue(parts[0])
|
||||
} else {
|
||||
InternStringTerm(b.Name)
|
||||
InternVarValue(b.Name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +95,7 @@ var DefaultBuiltins = [...]*Builtin{
|
||||
|
||||
// Arrays
|
||||
ArrayConcat,
|
||||
ArrayFlatten,
|
||||
ArraySlice,
|
||||
ArrayReverse,
|
||||
|
||||
@@ -146,6 +152,7 @@ var DefaultBuiltins = [...]*Builtin{
|
||||
Sprintf,
|
||||
StringReverse,
|
||||
RenderTemplate,
|
||||
InternalTemplateString,
|
||||
|
||||
// Numbers
|
||||
NumbersRange,
|
||||
@@ -887,6 +894,18 @@ var ArrayConcat = &Builtin{
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
var ArrayFlatten = &Builtin{
|
||||
Name: "array.flatten",
|
||||
Description: "Non-recursively unpacks array items in arr into the flattened array. Other types are appended as-is.",
|
||||
Decl: types.NewFunction(
|
||||
types.Args(
|
||||
types.Named("arr", types.NewArray(nil, types.A)).Description("the array to be flattened"),
|
||||
),
|
||||
types.Named("flattened", types.NewArray(nil, types.A)).Description("array flattened one level"),
|
||||
),
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
var ArraySlice = &Builtin{
|
||||
Name: "array.slice",
|
||||
Description: "Returns a slice of a given array. If `start` is greater or equal than `stop`, `slice` is `[]`.",
|
||||
@@ -1104,7 +1123,7 @@ var Concat = &Builtin{
|
||||
types.Named("output", types.S).Description("the joined string"),
|
||||
),
|
||||
Categories: stringsCat,
|
||||
CanSkipBctx: true,
|
||||
CanSkipBctx: false,
|
||||
}
|
||||
|
||||
var FormatInt = &Builtin{
|
||||
@@ -1272,7 +1291,7 @@ var Replace = &Builtin{
|
||||
types.Named("y", types.S).Description("string with replaced substrings"),
|
||||
),
|
||||
Categories: stringsCat,
|
||||
CanSkipBctx: true,
|
||||
CanSkipBctx: false,
|
||||
}
|
||||
|
||||
var ReplaceN = &Builtin{
|
||||
@@ -1292,7 +1311,7 @@ The old string comparisons are done in argument order.`,
|
||||
),
|
||||
types.Named("output", types.S).Description("string with replaced substrings"),
|
||||
),
|
||||
CanSkipBctx: true,
|
||||
CanSkipBctx: false,
|
||||
}
|
||||
|
||||
var RegexReplace = &Builtin{
|
||||
@@ -1656,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(
|
||||
@@ -1817,7 +1836,8 @@ var ObjectKeys = &Builtin{
|
||||
/*
|
||||
* Encoding
|
||||
*/
|
||||
var encoding = category("encoding")
|
||||
// Not using 'encoding' to avoid having to alias stdlib "encoding" imports
|
||||
var catEncoding = category("encoding")
|
||||
|
||||
var JSONMarshal = &Builtin{
|
||||
Name: "json.marshal",
|
||||
@@ -1828,7 +1848,7 @@ var JSONMarshal = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("the JSON string representation of `x`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -1850,7 +1870,7 @@ var JSONMarshalWithOptions = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("the JSON string representation of `x`, with configured prefix/indent string(s) as appropriate"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -1863,7 +1883,7 @@ var JSONUnmarshal = &Builtin{
|
||||
),
|
||||
types.Named("y", types.A).Description("the term deserialized from `x`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -1876,7 +1896,7 @@ var JSONIsValid = &Builtin{
|
||||
),
|
||||
types.Named("result", types.B).Description("`true` if `x` is valid JSON, `false` otherwise"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -1889,7 +1909,7 @@ var Base64Encode = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("base64 serialization of `x`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -1902,7 +1922,7 @@ var Base64Decode = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("base64 deserialization of `x`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -1915,7 +1935,7 @@ var Base64IsValid = &Builtin{
|
||||
),
|
||||
types.Named("result", types.B).Description("`true` if `x` is valid base64 encoded value, `false` otherwise"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -1928,7 +1948,7 @@ var Base64UrlEncode = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("base64url serialization of `x`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -1941,7 +1961,7 @@ var Base64UrlEncodeNoPad = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("base64url serialization of `x`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -1954,7 +1974,7 @@ var Base64UrlDecode = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("base64url deserialization of `x`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -1967,7 +1987,7 @@ var URLQueryDecode = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("URL-encoding deserialization of `x`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -1980,7 +2000,7 @@ var URLQueryEncode = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("URL-encoding serialization of `x`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -2004,7 +2024,7 @@ var URLQueryEncodeObject = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("the URL-encoded serialization of `object`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -2019,7 +2039,7 @@ var URLQueryDecodeObject = &Builtin{
|
||||
types.S,
|
||||
types.NewArray(nil, types.S)))).Description("the resulting object"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -2032,7 +2052,7 @@ var YAMLMarshal = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("the YAML string representation of `x`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -2045,7 +2065,7 @@ var YAMLUnmarshal = &Builtin{
|
||||
),
|
||||
types.Named("y", types.A).Description("the term deserialized from `x`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -2059,7 +2079,7 @@ var YAMLIsValid = &Builtin{
|
||||
),
|
||||
types.Named("result", types.B).Description("`true` if `x` is valid YAML, `false` otherwise"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -2072,7 +2092,7 @@ var HexEncode = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("serialization of `x` using hex-encoding"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -2085,7 +2105,7 @@ var HexDecode = &Builtin{
|
||||
),
|
||||
types.Named("y", types.S).Description("deserialized from `x`"),
|
||||
),
|
||||
Categories: encoding,
|
||||
Categories: catEncoding,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -3383,6 +3403,12 @@ var InternalTestCase = &Builtin{
|
||||
Decl: types.NewFunction([]types.Type{types.NewArray(nil, types.A)}, nil),
|
||||
}
|
||||
|
||||
var InternalTemplateString = &Builtin{
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated built-ins.
|
||||
*/
|
||||
@@ -3397,7 +3423,7 @@ var SetDiff = &Builtin{
|
||||
),
|
||||
types.SetOfAny,
|
||||
),
|
||||
deprecated: true,
|
||||
Deprecated: true,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -3411,7 +3437,7 @@ var NetCIDROverlap = &Builtin{
|
||||
),
|
||||
types.B,
|
||||
),
|
||||
deprecated: true,
|
||||
Deprecated: true,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -3423,7 +3449,7 @@ var CastArray = &Builtin{
|
||||
types.Args(types.A),
|
||||
types.NewArray(nil, types.A),
|
||||
),
|
||||
deprecated: true,
|
||||
Deprecated: true,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -3437,7 +3463,7 @@ var CastSet = &Builtin{
|
||||
types.Args(types.A),
|
||||
types.SetOfAny,
|
||||
),
|
||||
deprecated: true,
|
||||
Deprecated: true,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -3449,7 +3475,7 @@ var CastString = &Builtin{
|
||||
types.Args(types.A),
|
||||
types.S,
|
||||
),
|
||||
deprecated: true,
|
||||
Deprecated: true,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -3460,7 +3486,7 @@ var CastBoolean = &Builtin{
|
||||
types.Args(types.A),
|
||||
types.B,
|
||||
),
|
||||
deprecated: true,
|
||||
Deprecated: true,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -3471,7 +3497,7 @@ var CastNull = &Builtin{
|
||||
types.Args(types.A),
|
||||
types.Nl,
|
||||
),
|
||||
deprecated: true,
|
||||
Deprecated: true,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -3482,11 +3508,11 @@ var CastObject = &Builtin{
|
||||
types.Args(types.A),
|
||||
types.NewObject(nil, types.NewDynamicProperty(types.A, types.A)),
|
||||
),
|
||||
deprecated: true,
|
||||
Deprecated: true,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
// RegexMatchDeprecated declares `re_match` which has been deprecated. Use `regex.match` instead.
|
||||
// RegexMatchDeprecated declares `re_match` which has been Deprecated. Use `regex.match` instead.
|
||||
var RegexMatchDeprecated = &Builtin{
|
||||
Name: "re_match",
|
||||
Decl: types.NewFunction(
|
||||
@@ -3496,7 +3522,7 @@ var RegexMatchDeprecated = &Builtin{
|
||||
),
|
||||
types.B,
|
||||
),
|
||||
deprecated: true,
|
||||
Deprecated: true,
|
||||
CanSkipBctx: false,
|
||||
}
|
||||
|
||||
@@ -3513,7 +3539,7 @@ var All = &Builtin{
|
||||
),
|
||||
types.B,
|
||||
),
|
||||
deprecated: true,
|
||||
Deprecated: true,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -3530,7 +3556,7 @@ var Any = &Builtin{
|
||||
),
|
||||
types.B,
|
||||
),
|
||||
deprecated: true,
|
||||
Deprecated: true,
|
||||
CanSkipBctx: true,
|
||||
}
|
||||
|
||||
@@ -3548,7 +3574,7 @@ type Builtin struct {
|
||||
Decl *types.Function `json:"decl"` // Built-in function type declaration.
|
||||
Infix string `json:"infix,omitempty"` // Unique name of infix operator. Default should be unset.
|
||||
Relation bool `json:"relation,omitempty"` // Indicates if the built-in acts as a relation.
|
||||
deprecated bool `json:"-"` // Indicates if the built-in has been deprecated.
|
||||
Deprecated bool `json:"deprecated,omitempty"` // Indicates if the built-in has been deprecated.
|
||||
CanSkipBctx bool `json:"-"` // Built-in needs no data from the built-in context.
|
||||
Nondeterministic bool `json:"nondeterministic,omitempty"` // Indicates if the built-in returns non-deterministic results.
|
||||
}
|
||||
@@ -3573,12 +3599,12 @@ func (b *Builtin) Minimal() *Builtin {
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// IsDeprecated returns true if the Builtin function is deprecated and will be removed in a future release.
|
||||
// IsDeprecated returns true if the Builtin function is Deprecated and will be removed in a future release.
|
||||
func (b *Builtin) IsDeprecated() bool {
|
||||
return b.deprecated
|
||||
return b.Deprecated
|
||||
}
|
||||
|
||||
// IsDeterministic returns true if the Builtin function returns non-deterministic results.
|
||||
// IsNondeterministic returns true if the Builtin function returns non-deterministic results.
|
||||
func (b *Builtin) IsNondeterministic() bool {
|
||||
return b.Nondeterministic
|
||||
}
|
||||
|
||||
+9
-6
@@ -58,12 +58,14 @@ const FeatureRefHeads = "rule_head_refs"
|
||||
const FeatureRegoV1 = "rego_v1"
|
||||
const FeatureRegoV1Import = "rego_v1_import"
|
||||
const FeatureKeywordsInRefs = "keywords_in_refs"
|
||||
const FeatureTemplateStrings = "template_strings"
|
||||
|
||||
// Features carries the default features supported by this version of OPA.
|
||||
// Use RegisterFeatures to add to them.
|
||||
var Features = []string{
|
||||
FeatureRegoV1,
|
||||
FeatureKeywordsInRefs,
|
||||
FeatureTemplateStrings,
|
||||
}
|
||||
|
||||
// RegisterFeatures lets applications wrapping OPA register features, to be
|
||||
@@ -228,13 +230,8 @@ func LoadCapabilitiesVersions() ([]string, error) {
|
||||
// MinimumCompatibleVersion returns the minimum compatible OPA version based on
|
||||
// the built-ins, features, and keywords in c.
|
||||
func (c *Capabilities) MinimumCompatibleVersion() (string, bool) {
|
||||
var maxVersion semver.Version
|
||||
|
||||
// this is the oldest OPA release that includes capabilities
|
||||
if err := maxVersion.Set("0.17.0"); err != nil {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
maxVersion := semver.MustParse("0.17.0")
|
||||
minVersionIndex := minVersionIndexOnce()
|
||||
|
||||
for _, bi := range c.Builtins {
|
||||
@@ -274,6 +271,12 @@ func (c *Capabilities) ContainsFeature(feature string) bool {
|
||||
return slices.Contains(c.Features, feature)
|
||||
}
|
||||
|
||||
func (c *Capabilities) ContainsBuiltin(name string) bool {
|
||||
return slices.ContainsFunc(c.Builtins, func(builtin *Builtin) bool {
|
||||
return builtin.Name == name
|
||||
})
|
||||
}
|
||||
|
||||
// addBuiltinSorted inserts a built-in into c in sorted order. An existing built-in with the same name
|
||||
// will be overwritten.
|
||||
func (c *Capabilities) addBuiltinSorted(bi *Builtin) {
|
||||
|
||||
+85
-89
@@ -6,8 +6,8 @@ package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/types"
|
||||
@@ -16,11 +16,6 @@ import (
|
||||
|
||||
type varRewriter func(Ref) Ref
|
||||
|
||||
// exprChecker defines the interface for executing type checking on a single
|
||||
// expression. The exprChecker must update the provided TypeEnv with inferred
|
||||
// types of vars.
|
||||
type exprChecker func(*TypeEnv, *Expr) *Error
|
||||
|
||||
// typeChecker implements type checking on queries and rules. Errors are
|
||||
// accumulated on the typeChecker so that a single run can report multiple
|
||||
// issues.
|
||||
@@ -28,7 +23,6 @@ type typeChecker struct {
|
||||
builtins map[string]*Builtin
|
||||
required *Capabilities
|
||||
errs Errors
|
||||
exprCheckers map[string]exprChecker
|
||||
varRewriter varRewriter
|
||||
ss *SchemaSet
|
||||
allowNet []string
|
||||
@@ -39,11 +33,7 @@ type typeChecker struct {
|
||||
|
||||
// newTypeChecker returns a new typeChecker object that has no errors.
|
||||
func newTypeChecker() *typeChecker {
|
||||
return &typeChecker{
|
||||
exprCheckers: map[string]exprChecker{
|
||||
"eq": checkExprEq,
|
||||
},
|
||||
}
|
||||
return &typeChecker{}
|
||||
}
|
||||
|
||||
func (tc *typeChecker) newEnv(exist *TypeEnv) *TypeEnv {
|
||||
@@ -126,43 +116,39 @@ func (tc *typeChecker) Env(builtins map[string]*Builtin) *TypeEnv {
|
||||
// are found. The resulting TypeEnv wraps the provided one. The resulting
|
||||
// TypeEnv will be able to resolve types of vars contained in the body.
|
||||
func (tc *typeChecker) CheckBody(env *TypeEnv, body Body) (*TypeEnv, Errors) {
|
||||
var errors []*Error
|
||||
|
||||
errors := []*Error{}
|
||||
env = tc.newEnv(env)
|
||||
vis := newRefChecker(env, tc.varRewriter)
|
||||
gv := NewGenericVisitor(vis.Visit)
|
||||
|
||||
WalkExprs(body, func(expr *Expr) bool {
|
||||
for _, bexpr := range body {
|
||||
WalkExprs(bexpr, func(expr *Expr) bool {
|
||||
closureErrs := tc.checkClosures(env, expr)
|
||||
errors = append(errors, closureErrs...)
|
||||
|
||||
closureErrs := tc.checkClosures(env, expr)
|
||||
for _, err := range closureErrs {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
// reset errors from previous iteration
|
||||
vis.errs = nil
|
||||
gv.Walk(expr)
|
||||
errors = append(errors, vis.errs...)
|
||||
|
||||
hasClosureErrors := len(closureErrs) > 0
|
||||
|
||||
// reset errors from previous iteration
|
||||
vis.errs = nil
|
||||
NewGenericVisitor(vis.Visit).Walk(expr)
|
||||
for _, err := range vis.errs {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
|
||||
hasRefErrors := len(vis.errs) > 0
|
||||
|
||||
if err := tc.checkExpr(env, expr); err != nil {
|
||||
// Suppress this error if a more actionable one has occurred. In
|
||||
// this case, if an error occurred in a ref or closure contained in
|
||||
// this expression, and the error is due to a nil type, then it's
|
||||
// likely to be the result of the more specific error.
|
||||
skip := (hasClosureErrors || hasRefErrors) && causedByNilType(err)
|
||||
if !skip {
|
||||
errors = append(errors, err)
|
||||
if err := tc.checkExpr(env, expr); err != nil {
|
||||
hasClosureErrors := len(closureErrs) > 0
|
||||
hasRefErrors := len(vis.errs) > 0
|
||||
// Suppress this error if a more actionable one has occurred. In
|
||||
// this case, if an error occurred in a ref or closure contained in
|
||||
// this expression, and the error is due to a nil type, then it's
|
||||
// likely to be the result of the more specific error.
|
||||
skip := (hasClosureErrors || hasRefErrors) && causedByNilType(err)
|
||||
if !skip {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
tc.err(errors)
|
||||
tc.err(errors...)
|
||||
return env, errors
|
||||
}
|
||||
|
||||
@@ -243,7 +229,7 @@ func (tc *typeChecker) checkRule(env *TypeEnv, as *AnnotationSet, rule *Rule) {
|
||||
for _, schemaAnnot := range schemaAnnots {
|
||||
refType, err := tc.getSchemaType(schemaAnnot, rule)
|
||||
if err != nil {
|
||||
tc.err([]*Error{err})
|
||||
tc.err(err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -259,7 +245,7 @@ func (tc *typeChecker) checkRule(env *TypeEnv, as *AnnotationSet, rule *Rule) {
|
||||
} else {
|
||||
newType, err := override(ref[len(prefixRef):], t, refType, rule)
|
||||
if err != nil {
|
||||
tc.err([]*Error{err})
|
||||
tc.err(err)
|
||||
continue
|
||||
}
|
||||
env.tree.Put(prefixRef, newType)
|
||||
@@ -281,23 +267,25 @@ func (tc *typeChecker) checkRule(env *TypeEnv, as *AnnotationSet, rule *Rule) {
|
||||
var tpe types.Type
|
||||
|
||||
if len(rule.Head.Args) > 0 {
|
||||
// If args are not referred to in body, infer as any.
|
||||
WalkVars(rule.Head.Args, func(v Var) bool {
|
||||
if cpy.GetByValue(v) == nil {
|
||||
cpy.tree.PutOne(v, types.A)
|
||||
}
|
||||
return false
|
||||
})
|
||||
for _, arg := range rule.Head.Args {
|
||||
// If args are not referred to in body, infer as any.
|
||||
WalkTerms(arg, func(t *Term) bool {
|
||||
if _, ok := t.Value.(Var); ok {
|
||||
if cpy.GetByValue(t.Value) == nil {
|
||||
cpy.tree.PutOne(t.Value, types.A)
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
// Construct function type.
|
||||
args := make([]types.Type, len(rule.Head.Args))
|
||||
for i := range len(rule.Head.Args) {
|
||||
for i := range rule.Head.Args {
|
||||
args[i] = cpy.GetByValue(rule.Head.Args[i].Value)
|
||||
}
|
||||
|
||||
f := types.NewFunction(args, cpy.Get(rule.Head.Value))
|
||||
|
||||
tpe = f
|
||||
tpe = types.NewFunction(args, cpy.GetByValue(rule.Head.Value.Value))
|
||||
} else {
|
||||
switch rule.Head.RuleKind() {
|
||||
case SingleValue:
|
||||
@@ -310,7 +298,7 @@ func (tc *typeChecker) checkRule(env *TypeEnv, as *AnnotationSet, rule *Rule) {
|
||||
var err error
|
||||
tpe, err = nestedObject(cpy, objPath, typeV)
|
||||
if err != nil {
|
||||
tc.err([]*Error{NewError(TypeErr, rule.Head.Location, "%s", err.Error())})
|
||||
tc.err(NewError(TypeErr, rule.Head.Location, "%s", err.Error()))
|
||||
tpe = nil
|
||||
}
|
||||
} else if typeV != nil {
|
||||
@@ -374,19 +362,14 @@ func (tc *typeChecker) checkExpr(env *TypeEnv, expr *Expr) *Error {
|
||||
}
|
||||
}
|
||||
|
||||
checker := tc.exprCheckers[operator]
|
||||
if checker != nil {
|
||||
return checker(env, expr)
|
||||
if operator == "eq" {
|
||||
return checkExprEq(env, expr)
|
||||
}
|
||||
|
||||
return tc.checkExprBuiltin(env, expr)
|
||||
}
|
||||
|
||||
func (tc *typeChecker) checkExprBuiltin(env *TypeEnv, expr *Expr) *Error {
|
||||
|
||||
args := expr.Operands()
|
||||
pre := getArgTypes(env, args)
|
||||
|
||||
// NOTE(tsandall): undefined functions will have been caught earlier in the
|
||||
// compiler. We check for undefined functions before the safety check so
|
||||
// that references to non-existent functions result in undefined function
|
||||
@@ -405,10 +388,12 @@ func (tc *typeChecker) checkExprBuiltin(env *TypeEnv, expr *Expr) *Error {
|
||||
return NewError(TypeErr, expr.Location, "undefined function %v", name)
|
||||
}
|
||||
|
||||
// check if the expression refers to a function that contains an error
|
||||
_, ok := tpe.(types.Any)
|
||||
if ok {
|
||||
return nil
|
||||
if t, ok := tpe.(types.Any); ok {
|
||||
// A type.Any with a len(0) is created by using types.A , this represents a potential non-local reference
|
||||
// This is the exception when checking if the type represents a function
|
||||
if len(t) == 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
ftpe, ok := tpe.(*types.Function)
|
||||
@@ -424,12 +409,14 @@ func (tc *typeChecker) checkExprBuiltin(env *TypeEnv, expr *Expr) *Error {
|
||||
namedFargs.Args = append(namedFargs.Args, ftpe.NamedResult())
|
||||
}
|
||||
|
||||
args := expr.Operands()
|
||||
|
||||
if len(args) > len(fargs.Args) && fargs.Variadic == nil {
|
||||
return newArgError(expr.Location, name, "too many arguments", pre, namedFargs)
|
||||
return newArgError(expr.Location, name, "too many arguments", getArgTypes(env, args), namedFargs)
|
||||
}
|
||||
|
||||
if len(args) < len(ftpe.FuncArgs().Args) {
|
||||
return newArgError(expr.Location, name, "too few arguments", pre, namedFargs)
|
||||
return newArgError(expr.Location, name, "too few arguments", getArgTypes(env, args), namedFargs)
|
||||
}
|
||||
|
||||
for i := range args {
|
||||
@@ -601,7 +588,7 @@ func unify1(env *TypeEnv, term *Term, tpe types.Type, union bool) bool {
|
||||
return unifies
|
||||
}
|
||||
return false
|
||||
case Set:
|
||||
case *set:
|
||||
switch tpe := tpe.(type) {
|
||||
case *types.Set:
|
||||
return unify1Set(env, v, tpe, union)
|
||||
@@ -676,14 +663,14 @@ func unify1Object(env *TypeEnv, val Object, tpe *types.Object, union bool) bool
|
||||
return !stop
|
||||
}
|
||||
|
||||
func unify1Set(env *TypeEnv, val Set, tpe *types.Set, union bool) bool {
|
||||
func unify1Set(env *TypeEnv, val *set, tpe *types.Set, union bool) bool {
|
||||
of := types.Values(tpe)
|
||||
return !val.Until(func(elem *Term) bool {
|
||||
return !unify1(env, elem, of, union)
|
||||
})
|
||||
}
|
||||
|
||||
func (tc *typeChecker) err(errors []*Error) {
|
||||
func (tc *typeChecker) err(errors ...*Error) {
|
||||
tc.errs = append(tc.errs, errors...)
|
||||
}
|
||||
|
||||
@@ -704,7 +691,6 @@ func newRefChecker(env *TypeEnv, f varRewriter) *refChecker {
|
||||
|
||||
return &refChecker{
|
||||
env: env,
|
||||
errs: nil,
|
||||
varRewriter: f,
|
||||
}
|
||||
}
|
||||
@@ -716,8 +702,9 @@ func (rc *refChecker) Visit(x any) bool {
|
||||
case *Expr:
|
||||
switch terms := x.Terms.(type) {
|
||||
case []*Term:
|
||||
vis := NewGenericVisitor(rc.Visit)
|
||||
for i := 1; i < len(terms); i++ {
|
||||
NewGenericVisitor(rc.Visit).Walk(terms[i])
|
||||
vis.Walk(terms[i])
|
||||
}
|
||||
return true
|
||||
case *Term:
|
||||
@@ -807,7 +794,6 @@ func (rc *refChecker) checkRef(curr *TypeEnv, node *typeTreeNode, ref Ref, idx i
|
||||
}
|
||||
|
||||
func (rc *refChecker) checkRefLeaf(tpe types.Type, ref Ref, idx int) *Error {
|
||||
|
||||
if idx == len(ref) {
|
||||
return nil
|
||||
}
|
||||
@@ -822,16 +808,16 @@ func (rc *refChecker) checkRefLeaf(tpe types.Type, ref Ref, idx int) *Error {
|
||||
switch value := head.Value.(type) {
|
||||
|
||||
case Var:
|
||||
if exist := rc.env.GetByValue(value); exist != nil {
|
||||
if exist := rc.env.GetByValue(head.Value); exist != nil {
|
||||
if !unifies(exist, keys) {
|
||||
return newRefErrInvalid(ref[0].Location, rc.varRewriter(ref), idx, exist, keys, getOneOfForType(tpe))
|
||||
}
|
||||
} else {
|
||||
rc.env.tree.PutOne(value, types.Keys(tpe))
|
||||
rc.env.tree.PutOne(head.Value, types.Keys(tpe))
|
||||
}
|
||||
|
||||
case Ref:
|
||||
if exist := rc.env.Get(value); exist != nil {
|
||||
if exist := rc.env.GetByRef(value); exist != nil {
|
||||
if !unifies(exist, keys) {
|
||||
return newRefErrInvalid(ref[0].Location, rc.varRewriter(ref), idx, exist, keys, getOneOfForType(tpe))
|
||||
}
|
||||
@@ -1104,7 +1090,21 @@ func newRefErrInvalid(loc *Location, ref Ref, idx int, have, want types.Type, on
|
||||
}
|
||||
|
||||
func newRefErrUnsupported(loc *Location, ref Ref, idx int, have types.Type) *Error {
|
||||
err := newRefError(loc, ref)
|
||||
var err *Error
|
||||
switch have.(type) {
|
||||
case *types.Function:
|
||||
var function string
|
||||
// drop any trailing references to unidentified parameters (e.g. __local1__)
|
||||
if match, err := regexp.MatchString(`__local[0-9]+__`, ref[len(ref)-1].Value.String()); err == nil && match {
|
||||
function = ref[:len(ref)-1].String()
|
||||
} else {
|
||||
function = ref.String()
|
||||
}
|
||||
|
||||
err = NewError(TypeErr, loc, "function %s used as reference, not called", function)
|
||||
default:
|
||||
err = newRefError(loc, ref)
|
||||
}
|
||||
err.Details = &RefErrUnsupportedDetail{
|
||||
Ref: ref,
|
||||
Pos: idx,
|
||||
@@ -1132,7 +1132,7 @@ func getOneOfForNode(node *typeTreeNode) (result []Value) {
|
||||
return false
|
||||
})
|
||||
|
||||
sortValueSlice(result)
|
||||
slices.SortFunc(result, Value.Compare)
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1155,16 +1155,10 @@ func getOneOfForType(tpe types.Type) (result []Value) {
|
||||
}
|
||||
|
||||
result = removeDuplicate(result)
|
||||
sortValueSlice(result)
|
||||
slices.SortFunc(result, Value.Compare)
|
||||
return result
|
||||
}
|
||||
|
||||
func sortValueSlice(sl []Value) {
|
||||
sort.Slice(sl, func(i, j int) bool {
|
||||
return sl[i].Compare(sl[j]) < 0
|
||||
})
|
||||
}
|
||||
|
||||
func removeDuplicate(list []Value) []Value {
|
||||
seen := make(map[Value]bool)
|
||||
var newResult []Value
|
||||
@@ -1188,13 +1182,13 @@ func getArgTypes(env *TypeEnv, args []*Term) []types.Type {
|
||||
// getPrefix returns the shortest prefix of ref that exists in env
|
||||
func getPrefix(env *TypeEnv, ref Ref) (Ref, types.Type) {
|
||||
if len(ref) == 1 {
|
||||
t := env.Get(ref)
|
||||
t := env.GetByRef(ref)
|
||||
if t != nil {
|
||||
return ref, t
|
||||
}
|
||||
}
|
||||
for i := 1; i < len(ref); i++ {
|
||||
t := env.Get(ref[:i])
|
||||
t := env.GetByRef(ref[:i])
|
||||
if t != nil {
|
||||
return ref[:i], t
|
||||
}
|
||||
@@ -1202,12 +1196,14 @@ func getPrefix(env *TypeEnv, ref Ref) (Ref, types.Type) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var dynamicAnyAny = types.NewDynamicProperty(types.A, types.A)
|
||||
|
||||
// override takes a type t and returns a type obtained from t where the path represented by ref within it has type o (overriding the original type of that path)
|
||||
func override(ref Ref, t types.Type, o types.Type, rule *Rule) (types.Type, *Error) {
|
||||
var newStaticProps []*types.StaticProperty
|
||||
obj, ok := t.(*types.Object)
|
||||
if !ok {
|
||||
newType, err := getObjectType(ref, o, rule, types.NewDynamicProperty(types.A, types.A))
|
||||
newType, err := getObjectType(ref, o, rule, dynamicAnyAny)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+17
-18
@@ -96,6 +96,9 @@ func Compare(a, b any) int {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
case *TemplateString:
|
||||
b := b.(*TemplateString)
|
||||
return a.Compare(b)
|
||||
case Var:
|
||||
return VarCompare(a, b.(Var))
|
||||
case Ref:
|
||||
@@ -179,26 +182,28 @@ func sortOrder(x any) int {
|
||||
return 2
|
||||
case String:
|
||||
return 3
|
||||
case Var:
|
||||
case *TemplateString:
|
||||
return 4
|
||||
case Ref:
|
||||
case Var:
|
||||
return 5
|
||||
case *Array:
|
||||
case Ref:
|
||||
return 6
|
||||
case Object:
|
||||
case *Array:
|
||||
return 7
|
||||
case Set:
|
||||
case Object:
|
||||
return 8
|
||||
case *ArrayComprehension:
|
||||
case Set:
|
||||
return 9
|
||||
case *ObjectComprehension:
|
||||
case *ArrayComprehension:
|
||||
return 10
|
||||
case *SetComprehension:
|
||||
case *ObjectComprehension:
|
||||
return 11
|
||||
case Call:
|
||||
case *SetComprehension:
|
||||
return 12
|
||||
case Args:
|
||||
case Call:
|
||||
return 13
|
||||
case Args:
|
||||
return 14
|
||||
case *Expr:
|
||||
return 100
|
||||
case *SomeDecl:
|
||||
@@ -322,14 +327,6 @@ func TermValueEqual(a, b *Term) bool {
|
||||
}
|
||||
|
||||
func ValueEqual(a, b Value) bool {
|
||||
// TODO(ae): why doesn't this work the same?
|
||||
//
|
||||
// case interface{ Equal(Value) bool }:
|
||||
// return v.Equal(b)
|
||||
//
|
||||
// When put on top, golangci-lint even flags the other cases as unreachable..
|
||||
// but TestTopdownVirtualCache will have failing test cases when we replace
|
||||
// the other cases with the above one.. 🤔
|
||||
switch v := a.(type) {
|
||||
case Null:
|
||||
return v.Equal(b)
|
||||
@@ -345,6 +342,8 @@ func ValueEqual(a, b Value) bool {
|
||||
return v.Equal(b)
|
||||
case *Array:
|
||||
return v.Equal(b)
|
||||
case *TemplateString:
|
||||
return v.Equal(b)
|
||||
}
|
||||
|
||||
return a.Compare(b) == 0
|
||||
|
||||
+997
-422
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -33,7 +33,8 @@ func CompileModulesWithOpt(modules map[string]string, opts CompileOpts) (*Compil
|
||||
|
||||
compiler := NewCompiler().
|
||||
WithDefaultRegoVersion(opts.ParserOptions.RegoVersion).
|
||||
WithEnablePrintStatements(opts.EnablePrintStatements)
|
||||
WithEnablePrintStatements(opts.EnablePrintStatements).
|
||||
WithCapabilities(opts.ParserOptions.Capabilities)
|
||||
compiler.Compile(parsed)
|
||||
|
||||
if compiler.Failed() {
|
||||
|
||||
+2
-2
@@ -63,9 +63,9 @@ func checkDocumentConflicts(node *TreeNode, exists func([]string) (bool, error),
|
||||
if len(node.Values) > 0 {
|
||||
s := strings.Join(path, "/")
|
||||
if ok, err := exists(path); err != nil {
|
||||
return Errors{NewError(CompileErr, node.Values[0].(*Rule).Loc(), "conflict check for data path %v: %v", s, err.Error())}
|
||||
return Errors{NewError(CompileErr, node.Values[0].Loc(), "conflict check for data path %v: %v", s, err.Error())}
|
||||
} else if ok {
|
||||
return Errors{NewError(CompileErr, node.Values[0].(*Rule).Loc(), "conflicting rule for data path %v found", s)}
|
||||
return Errors{NewError(CompileErr, node.Values[0].Loc(), "conflicting rule for data path %v found", s)}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+28
-42
@@ -29,6 +29,7 @@ func newTypeEnv(f func() *typeChecker) *TypeEnv {
|
||||
}
|
||||
|
||||
// Get returns the type of x.
|
||||
//
|
||||
// Deprecated: Use GetByValue or GetByRef instead, as they are more efficient.
|
||||
func (env *TypeEnv) Get(x any) types.Type {
|
||||
if term, ok := x.(*Term); ok {
|
||||
@@ -53,15 +54,14 @@ func (env *TypeEnv) GetByValue(v Value) types.Type {
|
||||
return types.B
|
||||
case Number:
|
||||
return types.N
|
||||
case String:
|
||||
case String, *TemplateString:
|
||||
return types.S
|
||||
|
||||
// Composites.
|
||||
case *Array:
|
||||
static := make([]types.Type, x.Len())
|
||||
for i := range static {
|
||||
tpe := env.GetByValue(x.Elem(i).Value)
|
||||
static[i] = tpe
|
||||
static[i] = env.GetByValue(x.Elem(i).Value)
|
||||
}
|
||||
|
||||
var dynamic types.Type
|
||||
@@ -79,17 +79,13 @@ func (env *TypeEnv) GetByValue(v Value) types.Type {
|
||||
|
||||
x.Foreach(func(k, v *Term) {
|
||||
if IsConstant(k.Value) {
|
||||
kjson, err := JSON(k.Value)
|
||||
if err == nil {
|
||||
tpe := env.GetByValue(v.Value)
|
||||
static = append(static, types.NewStaticProperty(kjson, tpe))
|
||||
if kjson, err := JSON(k.Value); err == nil {
|
||||
static = append(static, types.NewStaticProperty(kjson, env.GetByValue(v.Value)))
|
||||
return
|
||||
}
|
||||
}
|
||||
// Can't handle it as a static property, fallback to dynamic
|
||||
typeK := env.GetByValue(k.Value)
|
||||
typeV := env.GetByValue(v.Value)
|
||||
dynamic = types.NewDynamicProperty(typeK, typeV)
|
||||
dynamic = types.NewDynamicProperty(env.GetByValue(k.Value), env.GetByValue(v.Value))
|
||||
})
|
||||
|
||||
if len(static) == 0 && dynamic == nil {
|
||||
@@ -98,7 +94,7 @@ func (env *TypeEnv) GetByValue(v Value) types.Type {
|
||||
|
||||
return types.NewObject(static, dynamic)
|
||||
|
||||
case Set:
|
||||
case *set:
|
||||
var tpe types.Type
|
||||
x.Foreach(func(elem *Term) {
|
||||
tpe = types.Or(tpe, env.GetByValue(elem.Value))
|
||||
@@ -161,12 +157,13 @@ func (env *TypeEnv) GetByRef(ref Ref) types.Type {
|
||||
}
|
||||
|
||||
func (env *TypeEnv) getRefFallback(ref Ref) types.Type {
|
||||
|
||||
if env.next != nil {
|
||||
return env.next.GetByRef(ref)
|
||||
}
|
||||
|
||||
if RootDocumentNames.Contains(ref[0]) {
|
||||
// types.A is an empty types.Any
|
||||
// this is used to represent a potential non-local reference
|
||||
return types.A
|
||||
}
|
||||
|
||||
@@ -298,15 +295,11 @@ func (n *typeTreeNode) PutOne(key Value, tpe types.Type) {
|
||||
func (n *typeTreeNode) Put(path Ref, tpe types.Type) {
|
||||
curr := n
|
||||
for _, term := range path {
|
||||
c, ok := curr.children.Get(term.Value)
|
||||
|
||||
var child *typeTreeNode
|
||||
child, ok := curr.children.Get(term.Value)
|
||||
if !ok {
|
||||
child = newTypeTree()
|
||||
child.key = term.Value
|
||||
curr.children.Put(child.key, child)
|
||||
} else {
|
||||
child = c
|
||||
}
|
||||
|
||||
curr = child
|
||||
@@ -320,23 +313,18 @@ func (n *typeTreeNode) Put(path Ref, tpe types.Type) {
|
||||
func (n *typeTreeNode) Insert(path Ref, tpe types.Type, env *TypeEnv) {
|
||||
curr := n
|
||||
for i, term := range path {
|
||||
c, ok := curr.children.Get(term.Value)
|
||||
|
||||
var child *typeTreeNode
|
||||
child, ok := curr.children.Get(term.Value)
|
||||
if !ok {
|
||||
child = newTypeTree()
|
||||
child.key = term.Value
|
||||
curr.children.Put(child.key, child)
|
||||
} else {
|
||||
child = c
|
||||
if child.value != nil && i+1 < len(path) {
|
||||
// If child has an object value, merge the new value into it.
|
||||
if o, ok := child.value.(*types.Object); ok {
|
||||
var err error
|
||||
child.value, err = insertIntoObject(o, path[i+1:], tpe, env)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("unreachable, insertIntoObject: %w", err))
|
||||
}
|
||||
} else if child.value != nil && i+1 < len(path) {
|
||||
// If child has an object value, merge the new value into it.
|
||||
if o, ok := child.value.(*types.Object); ok {
|
||||
var err error
|
||||
child.value, err = insertIntoObject(o, path[i+1:], tpe, env)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("unreachable, insertIntoObject: %w", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -348,8 +336,7 @@ func (n *typeTreeNode) Insert(path Ref, tpe types.Type, env *TypeEnv) {
|
||||
|
||||
if _, ok := tpe.(*types.Object); ok && curr.children.Len() > 0 {
|
||||
// merge all leafs into the inserted object
|
||||
leafs := curr.Leafs()
|
||||
for p, t := range leafs {
|
||||
for p, t := range curr.Leafs() {
|
||||
var err error
|
||||
curr.value, err = insertIntoObject(curr.value.(*types.Object), *p, t, env)
|
||||
if err != nil {
|
||||
@@ -387,7 +374,8 @@ func mergeTypes(a, b types.Type) types.Type {
|
||||
bDynProps := bObj.DynamicProperties()
|
||||
dynProps := types.NewDynamicProperty(
|
||||
types.Or(aDynProps.Key, bDynProps.Key),
|
||||
mergeTypes(aDynProps.Value, bDynProps.Value))
|
||||
mergeTypes(aDynProps.Value, bDynProps.Value),
|
||||
)
|
||||
return types.NewObject(nil, dynProps)
|
||||
} else if bAny, ok := b.(types.Any); ok && len(a.StaticProperties()) == 0 {
|
||||
// If a is an object type with no static components ...
|
||||
@@ -416,14 +404,14 @@ func mergeTypes(a, b types.Type) types.Type {
|
||||
}
|
||||
|
||||
func (n *typeTreeNode) String() string {
|
||||
b := strings.Builder{}
|
||||
b := &strings.Builder{}
|
||||
|
||||
key := "-"
|
||||
if k := n.key; k != nil {
|
||||
b.WriteString(k.String())
|
||||
} else {
|
||||
b.WriteString("-")
|
||||
key = k.String()
|
||||
}
|
||||
|
||||
b.WriteString(key)
|
||||
if v := n.value; v != nil {
|
||||
b.WriteString(": ")
|
||||
b.WriteString(v.String())
|
||||
@@ -431,9 +419,7 @@ func (n *typeTreeNode) String() string {
|
||||
|
||||
n.children.Iter(func(_ Value, child *typeTreeNode) bool {
|
||||
b.WriteString("\n\t+ ")
|
||||
s := child.String()
|
||||
s = strings.ReplaceAll(s, "\n", "\n\t")
|
||||
b.WriteString(s)
|
||||
b.WriteString(strings.ReplaceAll(child.String(), "\n", "\n\t"))
|
||||
|
||||
return false
|
||||
})
|
||||
@@ -484,7 +470,8 @@ func (n *typeTreeNode) Leafs() map[*Ref]types.Type {
|
||||
func collectLeafs(n *typeTreeNode, path Ref, leafs map[*Ref]types.Type) {
|
||||
nPath := append(path, NewTerm(n.key))
|
||||
if n.Leaf() {
|
||||
leafs[&nPath] = n.Value()
|
||||
npc := nPath // copy of else nPath escapes to heap even if !n.Leaf()
|
||||
leafs[&npc] = n.Value()
|
||||
return
|
||||
}
|
||||
n.children.Iter(func(_ Value, v *typeTreeNode) bool {
|
||||
@@ -512,7 +499,6 @@ func selectConstant(tpe types.Type, term *Term) types.Type {
|
||||
// contains vars or refs, then the returned type will be a union of the
|
||||
// possible types.
|
||||
func selectRef(tpe types.Type, ref Ref) types.Type {
|
||||
|
||||
if tpe == nil || len(ref) == 0 {
|
||||
return tpe
|
||||
}
|
||||
|
||||
+15
-6
@@ -99,26 +99,35 @@ func (e *Error) Error() string {
|
||||
}
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("%v: %v", e.Code, e.Message)
|
||||
|
||||
sb := strings.Builder{}
|
||||
if len(prefix) > 0 {
|
||||
msg = prefix + ": " + msg
|
||||
sb.WriteString(prefix)
|
||||
sb.WriteString(": ")
|
||||
}
|
||||
|
||||
sb.WriteString(e.Code)
|
||||
sb.WriteString(": ")
|
||||
sb.WriteString(e.Message)
|
||||
|
||||
if e.Details != nil {
|
||||
for _, line := range e.Details.Lines() {
|
||||
msg += "\n\t" + line
|
||||
sb.WriteString("\n\t")
|
||||
sb.WriteString(line)
|
||||
}
|
||||
}
|
||||
|
||||
return msg
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// NewError returns a new Error object.
|
||||
func NewError(code string, loc *Location, f string, a ...any) *Error {
|
||||
return newErrorString(code, loc, fmt.Sprintf(f, a...))
|
||||
}
|
||||
|
||||
func newErrorString(code string, loc *Location, m string) *Error {
|
||||
return &Error{
|
||||
Code: code,
|
||||
Location: loc,
|
||||
Message: fmt.Sprintf(f, a...),
|
||||
Message: m,
|
||||
}
|
||||
}
|
||||
|
||||
+32
-42
@@ -412,7 +412,7 @@ func (i *refindices) updateGlobMatch(rule *Rule, expr *Expr) {
|
||||
if _, ok := match.Value.(Var); ok {
|
||||
var ref Ref
|
||||
for _, other := range i.rules[rule] {
|
||||
if _, ok := other.Value.(Var); ok && other.Value.Compare(match.Value) == 0 {
|
||||
if ov, ok := other.Value.(Var); ok && ov.Equal(match.Value) {
|
||||
ref = other.Ref
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -884,7 +873,6 @@ func indexValue(b Value) (Value, bool) {
|
||||
}
|
||||
|
||||
func globDelimiterToString(delim *Term) (string, bool) {
|
||||
|
||||
arr, ok := delim.Value.(*Array)
|
||||
if !ok {
|
||||
return "", false
|
||||
@@ -895,14 +883,16 @@ func globDelimiterToString(delim *Term) (string, bool) {
|
||||
if arr.Len() == 0 {
|
||||
result = "."
|
||||
} else {
|
||||
sb := strings.Builder{}
|
||||
for i := range arr.Len() {
|
||||
term := arr.Elem(i)
|
||||
s, ok := term.Value.(String)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
result += string(s)
|
||||
sb.WriteString(string(s))
|
||||
}
|
||||
result = sb.String()
|
||||
}
|
||||
|
||||
return result, true
|
||||
|
||||
+148
-3
@@ -158,18 +158,42 @@ func (s *Scanner) WithoutKeywords(kws map[string]tokens.Token) (*Scanner, map[st
|
||||
return &cpy, kw
|
||||
}
|
||||
|
||||
type ScanOptions struct {
|
||||
continueTemplateString bool
|
||||
rawTemplateString bool
|
||||
}
|
||||
|
||||
type ScanOption func(*ScanOptions)
|
||||
|
||||
// ContinueTemplateString will continue scanning a template string
|
||||
func ContinueTemplateString(raw bool) ScanOption {
|
||||
return func(opts *ScanOptions) {
|
||||
opts.continueTemplateString = true
|
||||
opts.rawTemplateString = raw
|
||||
}
|
||||
}
|
||||
|
||||
// Scan will increment the scanners position in the source
|
||||
// code until the next token is found. The token, starting position
|
||||
// of the token, string literal, and any errors encountered are
|
||||
// returned. A token will always be returned, the caller must check
|
||||
// for any errors before using the other values.
|
||||
func (s *Scanner) Scan() (tokens.Token, Position, string, []Error) {
|
||||
func (s *Scanner) Scan(opts ...ScanOption) (tokens.Token, Position, string, []Error) {
|
||||
scanOpts := &ScanOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(scanOpts)
|
||||
}
|
||||
|
||||
pos := Position{Offset: s.offset - s.width, Row: s.row, Col: s.col, Tabs: s.tabs}
|
||||
var tok tokens.Token
|
||||
var lit string
|
||||
|
||||
if s.isWhitespace() {
|
||||
if scanOpts.continueTemplateString {
|
||||
if scanOpts.rawTemplateString {
|
||||
lit, tok = s.scanRawTemplateString()
|
||||
} else {
|
||||
lit, tok = s.scanTemplateString()
|
||||
}
|
||||
} else if s.isWhitespace() {
|
||||
// string(rune) is an unnecessary heap allocation in this case as we know all
|
||||
// the possible whitespace values, and can simply translate to string ourselves
|
||||
switch s.curr {
|
||||
@@ -275,6 +299,17 @@ func (s *Scanner) Scan() (tokens.Token, Position, string, []Error) {
|
||||
tok = tokens.Semicolon
|
||||
case '.':
|
||||
tok = tokens.Dot
|
||||
case '$':
|
||||
switch s.curr {
|
||||
case '`':
|
||||
s.next()
|
||||
lit, tok = s.scanRawTemplateString()
|
||||
case '"':
|
||||
s.next()
|
||||
lit, tok = s.scanTemplateString()
|
||||
default:
|
||||
s.error("illegal $ character")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,6 +430,116 @@ func (s *Scanner) scanRawString() string {
|
||||
return util.ByteSliceToString(s.bs[start : s.offset-1])
|
||||
}
|
||||
|
||||
func (s *Scanner) scanTemplateString() (string, tokens.Token) {
|
||||
tok := tokens.TemplateStringPart
|
||||
start := s.literalStart()
|
||||
var escapes []int
|
||||
for {
|
||||
ch := s.curr
|
||||
|
||||
if ch == '\n' || ch < 0 {
|
||||
s.error("non-terminated string")
|
||||
break
|
||||
}
|
||||
|
||||
s.next()
|
||||
|
||||
if ch == '"' {
|
||||
tok = tokens.TemplateStringEnd
|
||||
break
|
||||
}
|
||||
|
||||
if ch == '{' {
|
||||
break
|
||||
}
|
||||
|
||||
if ch == '\\' {
|
||||
switch s.curr {
|
||||
case '\\', '"', '/', 'b', 'f', 'n', 'r', 't':
|
||||
s.next()
|
||||
case '{':
|
||||
escapes = append(escapes, s.offset-1)
|
||||
s.next()
|
||||
case 'u':
|
||||
s.next()
|
||||
s.next()
|
||||
s.next()
|
||||
s.next()
|
||||
default:
|
||||
s.error("illegal escape sequence")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lazily remove escapes to not unnecessarily allocate a new byte slice
|
||||
if len(escapes) > 0 {
|
||||
return util.ByteSliceToString(removeEscapes(s, escapes, start)), tok
|
||||
}
|
||||
|
||||
return util.ByteSliceToString(s.bs[start : s.offset-1]), tok
|
||||
}
|
||||
|
||||
func (s *Scanner) scanRawTemplateString() (string, tokens.Token) {
|
||||
tok := tokens.RawTemplateStringPart
|
||||
start := s.literalStart()
|
||||
var escapes []int
|
||||
for {
|
||||
ch := s.curr
|
||||
|
||||
if ch < 0 {
|
||||
s.error("non-terminated string")
|
||||
break
|
||||
}
|
||||
|
||||
s.next()
|
||||
|
||||
if ch == '`' {
|
||||
tok = tokens.RawTemplateStringEnd
|
||||
break
|
||||
}
|
||||
|
||||
if ch == '{' {
|
||||
break
|
||||
}
|
||||
|
||||
if ch == '\\' {
|
||||
switch s.curr {
|
||||
case '{':
|
||||
escapes = append(escapes, s.offset-1)
|
||||
s.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lazily remove escapes to not unnecessarily allocate a new byte slice
|
||||
if len(escapes) > 0 {
|
||||
return util.ByteSliceToString(removeEscapes(s, escapes, start)), tok
|
||||
}
|
||||
|
||||
return util.ByteSliceToString(s.bs[start : s.offset-1]), tok
|
||||
}
|
||||
|
||||
func removeEscapes(s *Scanner, escapes []int, start int) []byte {
|
||||
from := start
|
||||
bs := make([]byte, 0, s.offset-start-len(escapes))
|
||||
|
||||
for _, escape := range escapes {
|
||||
// Append the bytes before the escape sequence.
|
||||
if escape > from {
|
||||
bs = append(bs, s.bs[from:escape-1]...)
|
||||
}
|
||||
// Skip the escape character.
|
||||
from = escape
|
||||
}
|
||||
|
||||
// Append the remaining bytes after the last escape sequence.
|
||||
if from < s.offset-1 {
|
||||
bs = append(bs, s.bs[from:s.offset-1]...)
|
||||
}
|
||||
|
||||
return bs
|
||||
}
|
||||
|
||||
func (s *Scanner) scanComment() string {
|
||||
start := s.literalStart()
|
||||
for s.curr != '\n' && s.curr != -1 {
|
||||
|
||||
+61
-47
@@ -39,6 +39,10 @@ const (
|
||||
|
||||
Number
|
||||
String
|
||||
TemplateStringPart
|
||||
TemplateStringEnd
|
||||
RawTemplateStringPart
|
||||
RawTemplateStringEnd
|
||||
|
||||
LBrack
|
||||
RBrack
|
||||
@@ -67,6 +71,7 @@ const (
|
||||
Lte
|
||||
Dot
|
||||
Semicolon
|
||||
Dollar
|
||||
|
||||
Every
|
||||
Contains
|
||||
@@ -74,53 +79,58 @@ const (
|
||||
)
|
||||
|
||||
var strings = [...]string{
|
||||
Illegal: "illegal",
|
||||
EOF: "eof",
|
||||
Whitespace: "whitespace",
|
||||
Comment: "comment",
|
||||
Ident: "identifier",
|
||||
Package: "package",
|
||||
Import: "import",
|
||||
As: "as",
|
||||
Default: "default",
|
||||
Else: "else",
|
||||
Not: "not",
|
||||
Some: "some",
|
||||
With: "with",
|
||||
Null: "null",
|
||||
True: "true",
|
||||
False: "false",
|
||||
Number: "number",
|
||||
String: "string",
|
||||
LBrack: "[",
|
||||
RBrack: "]",
|
||||
LBrace: "{",
|
||||
RBrace: "}",
|
||||
LParen: "(",
|
||||
RParen: ")",
|
||||
Comma: ",",
|
||||
Colon: ":",
|
||||
Add: "plus",
|
||||
Sub: "minus",
|
||||
Mul: "mul",
|
||||
Quo: "div",
|
||||
Rem: "rem",
|
||||
And: "and",
|
||||
Or: "or",
|
||||
Unify: "eq",
|
||||
Equal: "equal",
|
||||
Assign: "assign",
|
||||
In: "in",
|
||||
Neq: "neq",
|
||||
Gt: "gt",
|
||||
Lt: "lt",
|
||||
Gte: "gte",
|
||||
Lte: "lte",
|
||||
Dot: ".",
|
||||
Semicolon: ";",
|
||||
Every: "every",
|
||||
Contains: "contains",
|
||||
If: "if",
|
||||
Illegal: "illegal",
|
||||
EOF: "eof",
|
||||
Whitespace: "whitespace",
|
||||
Comment: "comment",
|
||||
Ident: "identifier",
|
||||
Package: "package",
|
||||
Import: "import",
|
||||
As: "as",
|
||||
Default: "default",
|
||||
Else: "else",
|
||||
Not: "not",
|
||||
Some: "some",
|
||||
With: "with",
|
||||
Null: "null",
|
||||
True: "true",
|
||||
False: "false",
|
||||
Number: "number",
|
||||
String: "string",
|
||||
TemplateStringPart: "template-string-part",
|
||||
TemplateStringEnd: "template-string-end",
|
||||
RawTemplateStringPart: "raw-template-string-part",
|
||||
RawTemplateStringEnd: "raw-template-string-end",
|
||||
LBrack: "[",
|
||||
RBrack: "]",
|
||||
LBrace: "{",
|
||||
RBrace: "}",
|
||||
LParen: "(",
|
||||
RParen: ")",
|
||||
Comma: ",",
|
||||
Colon: ":",
|
||||
Add: "plus",
|
||||
Sub: "minus",
|
||||
Mul: "mul",
|
||||
Quo: "div",
|
||||
Rem: "rem",
|
||||
And: "and",
|
||||
Or: "or",
|
||||
Unify: "eq",
|
||||
Equal: "equal",
|
||||
Assign: "assign",
|
||||
In: "in",
|
||||
Neq: "neq",
|
||||
Gt: "gt",
|
||||
Lt: "lt",
|
||||
Gte: "gte",
|
||||
Lte: "lte",
|
||||
Dot: ".",
|
||||
Semicolon: ";",
|
||||
Dollar: "dollar",
|
||||
Every: "every",
|
||||
Contains: "contains",
|
||||
If: "if",
|
||||
}
|
||||
|
||||
var keywords = map[string]Token{
|
||||
@@ -147,3 +157,7 @@ func IsKeyword(tok Token) bool {
|
||||
_, ok := keywords[strings[tok]]
|
||||
return ok
|
||||
}
|
||||
|
||||
func KeywordFor(tok Token) string {
|
||||
return strings[tok]
|
||||
}
|
||||
|
||||
+78
-14
@@ -5,6 +5,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"iter"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
@@ -19,27 +20,46 @@ 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 = ArrayTerm()
|
||||
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,
|
||||
}
|
||||
|
||||
internedVarValues = map[string]Value{
|
||||
"input": Var("input"),
|
||||
"data": Var("data"),
|
||||
"args": Var("args"),
|
||||
"schema": Var("schema"),
|
||||
"key": Var("key"),
|
||||
"value": Var("value"),
|
||||
"future": Var("future"),
|
||||
"rego": Var("rego"),
|
||||
"set": Var("set"),
|
||||
"internal": Var("internal"),
|
||||
"else": Var("else"),
|
||||
|
||||
"i": Var("i"), "j": Var("j"), "k": Var("k"), "v": Var("v"), "x": Var("x"), "y": Var("y"), "z": Var("z"),
|
||||
}
|
||||
)
|
||||
|
||||
// InternStringTerm interns the given strings as terms. Note that Interning is
|
||||
@@ -52,7 +72,21 @@ func InternStringTerm(str ...string) {
|
||||
continue
|
||||
}
|
||||
|
||||
internedStringTerms[s] = StringTerm(s)
|
||||
internedStringTerms[s] = &Term{Value: String(s)}
|
||||
}
|
||||
}
|
||||
|
||||
// InternVarValue interns the given variable names as Var Values. Note that Interning is
|
||||
// considered experimental and should not be relied upon by external code.
|
||||
// WARNING: This must **only** be called at initialization time, as the
|
||||
// interned terms are shared globally, and the underlying map is not thread-safe.
|
||||
func InternVarValue(names ...string) {
|
||||
for _, name := range names {
|
||||
if _, ok := internedVarValues[name]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
internedVarValues[name] = Var(name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +128,16 @@ func InternedValue[T internable](v T) Value {
|
||||
return InternedValueOr(v, internedTermValue)
|
||||
}
|
||||
|
||||
// InternedVarValue returns an interned Var Value for the given name. If the
|
||||
// name is not interned, a new Var Value is returned.
|
||||
func InternedVarValue(name string) Value {
|
||||
if v, ok := internedVarValues[name]; ok {
|
||||
return v
|
||||
}
|
||||
|
||||
return Var(name)
|
||||
}
|
||||
|
||||
// InternedValueOr returns an interned Value for scalar v. Calls supplier
|
||||
// to produce a Value if the value is not interned.
|
||||
func InternedValueOr[T internable](v T, supplier func(T) Value) Value {
|
||||
@@ -157,6 +201,13 @@ func InternedTerm[T internable](v T) *Term {
|
||||
}
|
||||
}
|
||||
|
||||
// InternedItem works just like [Item] but returns interned terms for both
|
||||
// key and value where possible. This is mostly useful for making tests less
|
||||
// verbose.
|
||||
func InternedItem[K, V internable](key K, value V) [2]*Term {
|
||||
return [2]*Term{InternedTerm(key), InternedTerm(value)}
|
||||
}
|
||||
|
||||
// InternedIntFromString returns a term with the given integer value if the string
|
||||
// maps to an interned term. If the string does not map to an interned term, nil is
|
||||
// returned.
|
||||
@@ -168,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 {
|
||||
@@ -206,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 {
|
||||
@@ -276,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
|
||||
|
||||
+32
-9
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
astJSON "github.com/open-policy-agent/opa/v1/ast/json"
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// Location records a position in source code
|
||||
@@ -28,10 +29,10 @@ func NewLocation(text []byte, file string, row int, col int) *Location {
|
||||
|
||||
// Equal checks if two locations are equal to each other.
|
||||
func (loc *Location) Equal(other *Location) bool {
|
||||
return bytes.Equal(loc.Text, other.Text) &&
|
||||
loc.File == other.File &&
|
||||
return loc.File == other.File &&
|
||||
loc.Row == other.Row &&
|
||||
loc.Col == other.Col
|
||||
loc.Col == other.Col &&
|
||||
bytes.Equal(loc.Text, other.Text)
|
||||
}
|
||||
|
||||
// Errorf returns a new error value with a message formatted to include the location
|
||||
@@ -57,13 +58,35 @@ func (loc *Location) Format(f string, a ...any) string {
|
||||
}
|
||||
|
||||
func (loc *Location) String() string {
|
||||
if len(loc.File) > 0 {
|
||||
return fmt.Sprintf("%v:%v", loc.File, loc.Row)
|
||||
buf, _ := loc.AppendText(make([]byte, 0, loc.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
func (loc *Location) AppendText(buf []byte) ([]byte, error) {
|
||||
if loc != nil {
|
||||
switch {
|
||||
case len(loc.File) > 0:
|
||||
buf = util.AppendInt(append(append(buf, loc.File...), ':'), loc.Row)
|
||||
case len(loc.Text) > 0:
|
||||
buf = append(buf, loc.Text...)
|
||||
default:
|
||||
buf = util.AppendInt(append(util.AppendInt(buf, loc.Row), ':'), loc.Col)
|
||||
}
|
||||
}
|
||||
if len(loc.Text) > 0 {
|
||||
return string(loc.Text)
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (loc *Location) StringLength() (n int) {
|
||||
if loc != nil {
|
||||
if l := len(loc.File); l > 0 {
|
||||
n = l + 1 + util.NumDigitsInt(loc.Row)
|
||||
} else if l := len(loc.Text); l > 0 {
|
||||
n = l
|
||||
} else {
|
||||
n = util.NumDigitsInt(loc.Row) + 1 + util.NumDigitsInt(loc.Col)
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%v:%v", loc.Row, loc.Col)
|
||||
return n
|
||||
}
|
||||
|
||||
// Compare returns -1, 0, or 1 to indicate if this loc is less than, equal to,
|
||||
@@ -71,7 +94,7 @@ func (loc *Location) String() string {
|
||||
// column of the Location (but not on the text.) Nil locations are greater than
|
||||
// non-nil locations.
|
||||
func (loc *Location) Compare(other *Location) int {
|
||||
if loc == nil && other == nil {
|
||||
if loc == other {
|
||||
return 0
|
||||
} else if loc == nil {
|
||||
return 1
|
||||
|
||||
+318
-174
@@ -20,12 +20,13 @@ import (
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/ast/internal/scanner"
|
||||
"github.com/open-policy-agent/opa/v1/ast/internal/tokens"
|
||||
astJSON "github.com/open-policy-agent/opa/v1/ast/json"
|
||||
"github.com/open-policy-agent/opa/v1/ast/location"
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// DefaultMaxParsingRecursionDepth is the default maximum recursion
|
||||
@@ -57,6 +58,25 @@ const (
|
||||
RegoV1
|
||||
)
|
||||
|
||||
var (
|
||||
// this is the name to use for instantiating an empty set, e.g., `set()`.
|
||||
setConstructor = RefTerm(VarTerm("set"))
|
||||
|
||||
preAllocWildcards = [...]Value{
|
||||
Var("$0"), Var("$1"), Var("$2"), Var("$3"), Var("$4"), Var("$5"),
|
||||
Var("$6"), Var("$7"), Var("$8"), Var("$9"), Var("$10"),
|
||||
}
|
||||
|
||||
// use static references to avoid allocations, and
|
||||
// copy them to the call term only when needed
|
||||
memberWithKeyRef = MemberWithKey.Ref()
|
||||
memberRef = Member.Ref()
|
||||
|
||||
newlineBytes = []byte{'\n'}
|
||||
metadataBytes = []byte("METADATA")
|
||||
metadataParserPool = util.NewSyncPool[metadataParser]()
|
||||
)
|
||||
|
||||
func (v RegoVersion) Int() int {
|
||||
if v == RegoV1 {
|
||||
return 1
|
||||
@@ -88,17 +108,17 @@ func RegoVersionFromInt(i int) RegoVersion {
|
||||
// can do efficient shallow copies of these values when doing a
|
||||
// save() and restore().
|
||||
type state struct {
|
||||
s *scanner.Scanner
|
||||
lastEnd int
|
||||
skippedNL bool
|
||||
tok tokens.Token
|
||||
tokEnd int
|
||||
lit string
|
||||
loc Location
|
||||
errors Errors
|
||||
hints []string
|
||||
comments []*Comment
|
||||
hints []string
|
||||
s *scanner.Scanner
|
||||
loc Location
|
||||
lit string
|
||||
lastEnd int
|
||||
tokEnd int
|
||||
wildcard int
|
||||
tok tokens.Token
|
||||
skippedNL bool
|
||||
}
|
||||
|
||||
func (s *state) String() string {
|
||||
@@ -451,7 +471,6 @@ func (p *Parser) Parse() ([]Statement, []*Comment, Errors) {
|
||||
// next type of statement. If a statement can be parsed, continue from that
|
||||
// point trying to parse packages, imports, etc. in the same order.
|
||||
for p.s.tok != tokens.EOF {
|
||||
|
||||
s := p.save()
|
||||
|
||||
if pkg := p.parsePackage(); pkg != nil {
|
||||
@@ -512,12 +531,12 @@ func (p *Parser) Parse() ([]Statement, []*Comment, Errors) {
|
||||
}
|
||||
|
||||
func (p *Parser) parseAnnotations(stmts []Statement) []Statement {
|
||||
|
||||
annotStmts, errs := parseAnnotations(p.s.comments)
|
||||
for _, err := range errs {
|
||||
p.error(err.Location, err.Message)
|
||||
}
|
||||
|
||||
stmts = slices.Grow(stmts, len(annotStmts))
|
||||
for _, annotStmt := range annotStmts {
|
||||
stmts = append(stmts, annotStmt)
|
||||
}
|
||||
@@ -525,53 +544,54 @@ func (p *Parser) parseAnnotations(stmts []Statement) []Statement {
|
||||
return stmts
|
||||
}
|
||||
|
||||
func parseAnnotations(comments []*Comment) ([]*Annotations, Errors) {
|
||||
func parseAnnotations(comments []*Comment) (stmts []*Annotations, errs Errors) {
|
||||
numBlocks := CountFunc(comments, isMetadataComment)
|
||||
if numBlocks == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var hint = []byte("METADATA")
|
||||
var curr *metadataParser
|
||||
var blocks []*metadataParser
|
||||
stmts = make([]*Annotations, 0, numBlocks)
|
||||
mdp := metadataParserPool.Get()
|
||||
if mdp.buf == nil {
|
||||
mdp.buf = &bytes.Buffer{}
|
||||
}
|
||||
|
||||
for i := range comments {
|
||||
if curr != nil {
|
||||
if comments[i].Location.Row == comments[i-1].Location.Row+1 && comments[i].Location.Col == 1 {
|
||||
curr.Append(comments[i])
|
||||
continue
|
||||
if isMetadataComment(comments[i]) { // scan until end of block
|
||||
mdp.Reset(comments[i].Location)
|
||||
for i++; i < len(comments) && !blockBuster(comments[i], comments[i-1]); i++ {
|
||||
mdp.Append(comments[i])
|
||||
}
|
||||
|
||||
if a, err := mdp.Parse(); err != nil {
|
||||
errs = append(errs, &Error{Code: ParseErr, Message: err.Error(), Location: mdp.loc})
|
||||
} else {
|
||||
stmts = append(stmts, a)
|
||||
}
|
||||
curr = nil
|
||||
}
|
||||
if bytes.HasPrefix(bytes.TrimSpace(comments[i].Text), hint) {
|
||||
curr = newMetadataParser(comments[i].Location)
|
||||
blocks = append(blocks, curr)
|
||||
}
|
||||
}
|
||||
|
||||
var stmts []*Annotations
|
||||
var errs Errors
|
||||
for _, b := range blocks {
|
||||
a, err := b.Parse()
|
||||
if err != nil {
|
||||
errs = append(errs, &Error{
|
||||
Code: ParseErr,
|
||||
Message: err.Error(),
|
||||
Location: b.loc,
|
||||
})
|
||||
} else {
|
||||
stmts = append(stmts, a)
|
||||
}
|
||||
}
|
||||
metadataParserPool.Put(mdp)
|
||||
|
||||
return stmts, errs
|
||||
}
|
||||
|
||||
func isMetadataComment(c *Comment) bool {
|
||||
return c.Location.Col == 1 && bytes.HasPrefix(bytes.TrimSpace(c.Text), metadataBytes)
|
||||
}
|
||||
|
||||
func blockBuster(curr, prev *Comment) bool { // or endOfBlock, but the name was too good to pass up
|
||||
return curr.Location.Col != 1 || curr.Location.Row-1 != prev.Location.Row
|
||||
}
|
||||
|
||||
func (p *Parser) parsePackage() *Package {
|
||||
|
||||
var pkg Package
|
||||
pkg.SetLoc(p.s.Loc())
|
||||
|
||||
if p.s.tok != tokens.Package {
|
||||
return nil
|
||||
}
|
||||
|
||||
var pkg Package
|
||||
pkg.SetLoc(p.s.Loc())
|
||||
|
||||
p.scanWS()
|
||||
|
||||
// Make sure we allow the first term of refs to be the 'package' keyword.
|
||||
@@ -633,14 +653,13 @@ func (p *Parser) parsePackage() *Package {
|
||||
}
|
||||
|
||||
func (p *Parser) parseImport() *Import {
|
||||
|
||||
var imp Import
|
||||
imp.SetLoc(p.s.Loc())
|
||||
|
||||
if p.s.tok != tokens.Import {
|
||||
return nil
|
||||
}
|
||||
|
||||
var imp Import
|
||||
imp.SetLoc(p.s.Loc())
|
||||
|
||||
p.scanWS()
|
||||
|
||||
// Make sure we allow the first term of refs to be the 'import' keyword.
|
||||
@@ -952,7 +971,7 @@ func (p *Parser) parseRules() []*Rule {
|
||||
next.Head.keywords = rule.Head.keywords
|
||||
for i := range next.Head.Args {
|
||||
if v, ok := next.Head.Args[i].Value.(Var); ok && v.IsWildcard() {
|
||||
next.Head.Args[i].Value = Var(p.genwildcard())
|
||||
next.Head.Args[i].Value = p.genwildcard()
|
||||
}
|
||||
}
|
||||
setLocRecursive(next.Head, loc)
|
||||
@@ -972,7 +991,7 @@ func (p *Parser) parseElse(head *Head) *Rule {
|
||||
rule.Head.generatedValue = false
|
||||
for i := range rule.Head.Args {
|
||||
if v, ok := rule.Head.Args[i].Value.(Var); ok && v.IsWildcard() {
|
||||
rule.Head.Args[i].Value = Var(p.genwildcard())
|
||||
rule.Head.Args[i].Value = p.genwildcard()
|
||||
}
|
||||
}
|
||||
rule.Head.SetLoc(p.s.Loc())
|
||||
@@ -1281,14 +1300,11 @@ func (p *Parser) parseLiteralExpr(negated bool) *Expr {
|
||||
}
|
||||
|
||||
func (p *Parser) parseWith() []*With {
|
||||
|
||||
withs := []*With{}
|
||||
|
||||
for {
|
||||
with := With{Location: p.s.Loc()}
|
||||
|
||||
with := With{
|
||||
Location: p.s.Loc(),
|
||||
}
|
||||
p.scan()
|
||||
|
||||
if p.s.tok != tokens.Ident {
|
||||
@@ -1525,11 +1541,6 @@ func (p *Parser) parseTermInfixCallInList() *Term {
|
||||
return p.parseTermIn(nil, false, p.s.loc.Offset)
|
||||
}
|
||||
|
||||
// use static references to avoid allocations, and
|
||||
// copy them to the call term only when needed
|
||||
var memberWithKeyRef = MemberWithKey.Ref()
|
||||
var memberRef = Member.Ref()
|
||||
|
||||
func (p *Parser) parseTermIn(lhs *Term, keyVal bool, offset int) *Term {
|
||||
if !p.enter() {
|
||||
return nil
|
||||
@@ -1731,6 +1742,10 @@ func (p *Parser) parseTerm() *Term {
|
||||
term = p.parseNumber()
|
||||
case tokens.String:
|
||||
term = p.parseString()
|
||||
case tokens.TemplateStringPart, tokens.TemplateStringEnd:
|
||||
term = p.parseTemplateString(false)
|
||||
case tokens.RawTemplateStringPart, tokens.RawTemplateStringEnd:
|
||||
term = p.parseTemplateString(true)
|
||||
case tokens.Ident, tokens.Contains: // NOTE(sr): contains anywhere BUT in rule heads gets no special treatment
|
||||
term = p.parseVar()
|
||||
case tokens.LBrack:
|
||||
@@ -1762,7 +1777,7 @@ func (p *Parser) parseTermFinish(head *Term, skipws bool) *Term {
|
||||
return nil
|
||||
}
|
||||
offset := p.s.loc.Offset
|
||||
p.doScan(skipws)
|
||||
p.doScan(skipws, noScanOptions...)
|
||||
|
||||
switch p.s.tok {
|
||||
case tokens.LParen, tokens.Dot, tokens.LBrack:
|
||||
@@ -1783,7 +1798,7 @@ func (p *Parser) parseHeadFinish(head *Term, skipws bool) *Term {
|
||||
return nil
|
||||
}
|
||||
offset := p.s.loc.Offset
|
||||
p.doScan(false)
|
||||
p.scanWS()
|
||||
|
||||
switch p.s.tok {
|
||||
case tokens.Add, tokens.Sub, tokens.Mul, tokens.Quo, tokens.Rem,
|
||||
@@ -1791,7 +1806,7 @@ func (p *Parser) parseHeadFinish(head *Term, skipws bool) *Term {
|
||||
tokens.Equal, tokens.Neq, tokens.Gt, tokens.Gte, tokens.Lt, tokens.Lte:
|
||||
p.illegalToken()
|
||||
case tokens.Whitespace:
|
||||
p.doScan(skipws)
|
||||
p.doScan(skipws, noScanOptions...)
|
||||
}
|
||||
|
||||
switch p.s.tok {
|
||||
@@ -1878,7 +1893,12 @@ 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]
|
||||
if !strings.ContainsRune(inner, '\\') { // nothing to un-escape
|
||||
return StringTerm(inner).SetLocation(p.s.Loc())
|
||||
}
|
||||
|
||||
var s string
|
||||
@@ -1898,8 +1918,119 @@ func (p *Parser) parseRawString() *Term {
|
||||
return StringTerm(p.s.lit[1 : len(p.s.lit)-1]).SetLocation(p.s.Loc())
|
||||
}
|
||||
|
||||
// this is the name to use for instantiating an empty set, e.g., `set()`.
|
||||
var setConstructor = RefTerm(VarTerm("set"))
|
||||
func templateStringPartToStringLiteral(tok tokens.Token, lit string) (string, error) {
|
||||
switch tok {
|
||||
case tokens.TemplateStringPart, tokens.TemplateStringEnd:
|
||||
inner := lit[1 : len(lit)-1]
|
||||
if !strings.ContainsRune(inner, '\\') { // nothing to un-escape
|
||||
return inner, nil
|
||||
}
|
||||
|
||||
buf := make([]byte, 0, len(inner)+2)
|
||||
buf = append(buf, '"')
|
||||
buf = append(buf, inner...)
|
||||
buf = append(buf, '"')
|
||||
var s string
|
||||
if err := json.Unmarshal(buf, &s); err != nil {
|
||||
return "", fmt.Errorf("illegal template-string part: %s", lit)
|
||||
}
|
||||
return s, nil
|
||||
case tokens.RawTemplateStringPart, tokens.RawTemplateStringEnd:
|
||||
return lit[1 : len(lit)-1], nil
|
||||
default:
|
||||
return "", errors.New("expected template-string part")
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) parseTemplateString(multiLine bool) *Term {
|
||||
loc := p.s.Loc()
|
||||
|
||||
if !p.po.Capabilities.ContainsFeature(FeatureTemplateStrings) {
|
||||
p.errorf(loc, "template strings are not supported by current capabilities")
|
||||
return nil
|
||||
}
|
||||
|
||||
var parts []Node
|
||||
|
||||
for {
|
||||
s, err := templateStringPartToStringLiteral(p.s.tok, p.s.lit)
|
||||
if err != nil {
|
||||
p.error(p.s.Loc(), err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Don't add empty strings
|
||||
if len(s) > 0 {
|
||||
parts = append(parts, StringTerm(s).SetLocation(p.s.Loc()))
|
||||
}
|
||||
|
||||
if p.s.tok == tokens.TemplateStringEnd || p.s.tok == tokens.RawTemplateStringEnd {
|
||||
break
|
||||
}
|
||||
|
||||
numCommentsBefore := len(p.s.comments)
|
||||
p.scan()
|
||||
numCommentsAfter := len(p.s.comments)
|
||||
|
||||
expr := p.parseLiteral()
|
||||
if expr == nil {
|
||||
p.error(p.s.Loc(), "invalid template-string expression")
|
||||
return nil
|
||||
}
|
||||
|
||||
if expr.Negated {
|
||||
p.errorf(expr.Loc(), "unexpected negation ('%s') in template-string expression", tokens.KeywordFor(tokens.Not))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Note: Actually unification
|
||||
if expr.IsEquality() {
|
||||
p.errorf(expr.Loc(), "unexpected unification ('=') in template-string expression")
|
||||
return nil
|
||||
}
|
||||
|
||||
if expr.IsAssignment() {
|
||||
p.errorf(expr.Loc(), "unexpected assignment (':=') in template-string expression")
|
||||
return nil
|
||||
}
|
||||
|
||||
if expr.IsEvery() {
|
||||
p.errorf(expr.Loc(), "unexpected '%s' in template-string expression", tokens.KeywordFor(tokens.Every))
|
||||
return nil
|
||||
}
|
||||
|
||||
if expr.IsSome() {
|
||||
p.errorf(expr.Loc(), "unexpected '%s' in template-string expression", tokens.KeywordFor(tokens.Some))
|
||||
return nil
|
||||
}
|
||||
|
||||
// FIXME: Can we optimize for collections and comprehensions too? To qualify, they must not contain refs or calls.
|
||||
var nonOptional bool
|
||||
if term, ok := expr.Terms.(*Term); ok && numCommentsAfter == numCommentsBefore {
|
||||
switch term.Value.(type) {
|
||||
case String, Number, Boolean, Null:
|
||||
nonOptional = true
|
||||
parts = append(parts, term)
|
||||
}
|
||||
}
|
||||
|
||||
if !nonOptional {
|
||||
parts = append(parts, expr)
|
||||
}
|
||||
|
||||
if p.s.tok != tokens.RBrace {
|
||||
p.errorf(p.s.Loc(), "expected %s to end template string expression", tokens.RBrace)
|
||||
return nil
|
||||
}
|
||||
|
||||
p.doScan(false, scanner.ContinueTemplateString(multiLine))
|
||||
}
|
||||
|
||||
// When there are template-expressions, the initial location will only contain the text up to the first expression
|
||||
loc.Text = p.s.Text(loc.Offset, p.s.tokEnd)
|
||||
|
||||
return TemplateStringTerm(multiLine, parts...).SetLocation(loc)
|
||||
}
|
||||
|
||||
func (p *Parser) parseCall(operator *Term, offset int) (term *Term) {
|
||||
if !p.enter() {
|
||||
@@ -1978,7 +2109,7 @@ func (p *Parser) parseRef(head *Term, offset int) (term *Term) {
|
||||
term = p.parseRef(term, offset)
|
||||
}
|
||||
}
|
||||
end = p.s.tokEnd
|
||||
end = p.s.lastEnd
|
||||
return term
|
||||
case tokens.LBrack:
|
||||
p.scan()
|
||||
@@ -2042,7 +2173,6 @@ func (p *Parser) parseArray() (term *Term) {
|
||||
// Does this represent a set comprehension or a set containing binary OR
|
||||
// call? We resolve the ambiguity by prioritizing comprehensions.
|
||||
head := p.parseTerm()
|
||||
|
||||
if head == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -2286,7 +2416,7 @@ func (p *Parser) parseTermList(end tokens.Token, r []*Term) []*Term {
|
||||
}
|
||||
continue
|
||||
default:
|
||||
p.illegal(fmt.Sprintf("expected %q or %q", tokens.Comma, end))
|
||||
p.illegal("expected %q or %q", tokens.Comma, end)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -2316,12 +2446,12 @@ func (p *Parser) parseTermPairList(end tokens.Token, r [][2]*Term) [][2]*Term {
|
||||
}
|
||||
continue
|
||||
default:
|
||||
p.illegal(fmt.Sprintf("expected %q or %q", tokens.Comma, end))
|
||||
p.illegal("expected %q or %q", tokens.Comma, end)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
default:
|
||||
p.illegal(fmt.Sprintf("expected %q", tokens.Colon))
|
||||
p.illegal("expected %q", tokens.Colon)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -2331,7 +2461,8 @@ func (p *Parser) parseTermPairList(end tokens.Token, r [][2]*Term) [][2]*Term {
|
||||
|
||||
func (p *Parser) parseTermOp(values ...tokens.Token) *Term {
|
||||
if slices.Contains(values, p.s.tok) {
|
||||
r := RefTerm(VarTerm(p.s.tok.String()).SetLocation(p.s.Loc())).SetLocation(p.s.Loc())
|
||||
loc := p.s.Loc()
|
||||
r := RefTerm(VarTerm(p.s.tok.String()).SetLocation(loc)).SetLocation(loc)
|
||||
p.scan()
|
||||
return r
|
||||
}
|
||||
@@ -2341,11 +2472,12 @@ func (p *Parser) parseTermOp(values ...tokens.Token) *Term {
|
||||
func (p *Parser) parseTermOpName(ref Ref, values ...tokens.Token) *Term {
|
||||
if slices.Contains(values, p.s.tok) {
|
||||
cp := ref.Copy()
|
||||
loc := p.s.Loc()
|
||||
for _, r := range cp {
|
||||
r.SetLocation(p.s.Loc())
|
||||
r.SetLocation(loc)
|
||||
}
|
||||
t := RefTerm(cp...)
|
||||
t.SetLocation(p.s.Loc())
|
||||
t.SetLocation(loc)
|
||||
p.scan()
|
||||
return t
|
||||
}
|
||||
@@ -2353,48 +2485,69 @@ func (p *Parser) parseTermOpName(ref Ref, values ...tokens.Token) *Term {
|
||||
}
|
||||
|
||||
func (p *Parser) parseVar() *Term {
|
||||
|
||||
s := p.s.lit
|
||||
|
||||
term := VarTerm(s).SetLocation(p.s.Loc())
|
||||
|
||||
// Update wildcard values with unique identifiers
|
||||
if term.Equal(Wildcard) {
|
||||
term.Value = Var(p.genwildcard())
|
||||
if p.s.lit == WildcardString {
|
||||
// Update wildcard values with unique identifiers
|
||||
return NewTerm(p.genwildcard()).SetLocation(p.s.Loc())
|
||||
}
|
||||
|
||||
return term
|
||||
return VarTerm(p.s.lit).SetLocation(p.s.Loc())
|
||||
}
|
||||
|
||||
func (p *Parser) genwildcard() string {
|
||||
c := p.s.wildcard
|
||||
func (p *Parser) genwildcard() Value {
|
||||
var v Value
|
||||
if p.s.wildcard < len(preAllocWildcards) {
|
||||
v = preAllocWildcards[p.s.wildcard]
|
||||
} else {
|
||||
v = Var(WildcardPrefix + strconv.Itoa(p.s.wildcard))
|
||||
}
|
||||
p.s.wildcard++
|
||||
return fmt.Sprintf("%v%d", WildcardPrefix, c)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func (p *Parser) error(loc *location.Location, reason string) {
|
||||
p.errorf(loc, "%s", reason)
|
||||
}
|
||||
|
||||
func (p *Parser) errorf(loc *location.Location, f string, a ...any) {
|
||||
msg := strings.Builder{}
|
||||
msg.WriteString(fmt.Sprintf(f, a...))
|
||||
|
||||
switch len(p.s.hints) {
|
||||
func writeHints(msg *strings.Builder, hints []string) {
|
||||
switch len(hints) {
|
||||
case 0: // nothing to do
|
||||
case 1:
|
||||
msg.WriteString(" (hint: ")
|
||||
msg.WriteString(p.s.hints[0])
|
||||
msg.WriteRune(')')
|
||||
msg.WriteString(hints[0])
|
||||
msg.WriteByte(')')
|
||||
default:
|
||||
msg.WriteString(" (hints: ")
|
||||
for i, h := range p.s.hints {
|
||||
for i, h := range hints {
|
||||
if i > 0 {
|
||||
msg.WriteString(", ")
|
||||
}
|
||||
msg.WriteString(h)
|
||||
}
|
||||
msg.WriteRune(')')
|
||||
msg.WriteByte(')')
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) error(loc *location.Location, reason string) {
|
||||
msg := reason
|
||||
if len(p.s.hints) > 0 {
|
||||
sb := &strings.Builder{}
|
||||
sb.WriteString(reason)
|
||||
writeHints(sb, p.s.hints)
|
||||
msg = sb.String()
|
||||
}
|
||||
|
||||
p.s.errors = append(p.s.errors, &Error{
|
||||
Code: ParseErr,
|
||||
Message: msg,
|
||||
Location: loc,
|
||||
Details: newParserErrorDetail(p.s.s.Bytes(), loc.Offset),
|
||||
})
|
||||
p.s.hints = nil
|
||||
}
|
||||
|
||||
func (p *Parser) errorf(loc *location.Location, f string, a ...any) {
|
||||
msg := &strings.Builder{}
|
||||
fmt.Fprintf(msg, f, a...)
|
||||
|
||||
if len(p.s.hints) > 0 {
|
||||
writeHints(msg, p.s.hints)
|
||||
}
|
||||
|
||||
p.s.errors = append(p.s.errors, &Error{
|
||||
@@ -2406,28 +2559,25 @@ func (p *Parser) errorf(loc *location.Location, f string, a ...any) {
|
||||
p.s.hints = nil
|
||||
}
|
||||
|
||||
func (p *Parser) hint(f string, a ...any) {
|
||||
p.s.hints = append(p.s.hints, fmt.Sprintf(f, a...))
|
||||
func (p *Parser) hint(s string) {
|
||||
p.s.hints = append(p.s.hints, s)
|
||||
}
|
||||
|
||||
func (p *Parser) illegal(note string, a ...any) {
|
||||
tok := p.s.tok.String()
|
||||
|
||||
if p.s.tok == tokens.Illegal {
|
||||
p.errorf(p.s.Loc(), "illegal token")
|
||||
return
|
||||
}
|
||||
|
||||
tok := p.s.tok.String()
|
||||
|
||||
tokType := "token"
|
||||
if tokens.IsKeyword(p.s.tok) {
|
||||
tokType = "keyword"
|
||||
} else if _, ok := allFutureKeywords[p.s.tok.String()]; ok {
|
||||
if _, ok := allFutureKeywords[tok]; ok || tokens.IsKeyword(p.s.tok) {
|
||||
tokType = "keyword"
|
||||
}
|
||||
|
||||
note = fmt.Sprintf(note, a...)
|
||||
if len(note) > 0 {
|
||||
p.errorf(p.s.Loc(), "unexpected %s %s: %s", tok, tokType, note)
|
||||
p.errorf(p.s.Loc(), "unexpected %s %s: %s", tok, tokType, fmt.Sprintf(note, a...))
|
||||
} else {
|
||||
p.errorf(p.s.Loc(), "unexpected %s %s", tok, tokType)
|
||||
}
|
||||
@@ -2437,15 +2587,17 @@ func (p *Parser) illegalToken() {
|
||||
p.illegal("")
|
||||
}
|
||||
|
||||
var noScanOptions []scanner.ScanOption
|
||||
|
||||
func (p *Parser) scan() {
|
||||
p.doScan(true)
|
||||
p.doScan(true, noScanOptions...)
|
||||
}
|
||||
|
||||
func (p *Parser) scanWS() {
|
||||
p.doScan(false)
|
||||
p.doScan(false, noScanOptions...)
|
||||
}
|
||||
|
||||
func (p *Parser) doScan(skipws bool) {
|
||||
func (p *Parser) doScan(skipws bool, scanOpts ...scanner.ScanOption) {
|
||||
|
||||
// NOTE(tsandall): the last position is used to compute the "text" field for
|
||||
// complex AST nodes. Whitespace never affects the last position of an AST
|
||||
@@ -2458,7 +2610,7 @@ func (p *Parser) doScan(skipws bool) {
|
||||
var errs []scanner.Error
|
||||
for {
|
||||
var pos scanner.Position
|
||||
p.s.tok, pos, p.s.lit, errs = p.s.s.Scan()
|
||||
p.s.tok, pos, p.s.lit, errs = p.s.s.Scan(scanOpts...)
|
||||
|
||||
p.s.tokEnd = pos.End
|
||||
p.s.loc.Row = pos.Row
|
||||
@@ -2513,12 +2665,10 @@ func (p *Parser) restore(s *state) {
|
||||
}
|
||||
|
||||
func setLocRecursive(x any, loc *location.Location) {
|
||||
NewGenericVisitor(func(x any) bool {
|
||||
if node, ok := x.(Node); ok {
|
||||
node.SetLoc(loc)
|
||||
}
|
||||
WalkNodes(x, func(n Node) bool {
|
||||
n.SetLoc(loc)
|
||||
return false
|
||||
}).Walk(x)
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Parser) setLoc(term *Term, loc *location.Location, offset, end int) *Term {
|
||||
@@ -2601,13 +2751,17 @@ type rawAnnotation struct {
|
||||
}
|
||||
|
||||
type metadataParser struct {
|
||||
buf *bytes.Buffer
|
||||
comments []*Comment
|
||||
buf *bytes.Buffer
|
||||
loc *location.Location
|
||||
}
|
||||
|
||||
func newMetadataParser(loc *Location) *metadataParser {
|
||||
return &metadataParser{loc: loc, buf: bytes.NewBuffer(nil)}
|
||||
func (b *metadataParser) Reset(loc *location.Location) {
|
||||
b.comments = b.comments[:0]
|
||||
b.loc = loc
|
||||
if b.buf != nil {
|
||||
b.buf.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *metadataParser) Append(c *Comment) {
|
||||
@@ -2618,14 +2772,12 @@ func (b *metadataParser) Append(c *Comment) {
|
||||
|
||||
var yamlLineErrRegex = regexp.MustCompile(`^yaml:(?: unmarshal errors:[\n\s]*)? line ([[:digit:]]+):`)
|
||||
|
||||
func (b *metadataParser) Parse() (*Annotations, error) {
|
||||
|
||||
var raw rawAnnotation
|
||||
|
||||
func (b *metadataParser) Parse() (result *Annotations, err error) {
|
||||
if len(bytes.TrimSpace(b.buf.Bytes())) == 0 {
|
||||
return nil, errors.New("expected METADATA block, found whitespace")
|
||||
}
|
||||
|
||||
var raw rawAnnotation
|
||||
if err := yaml.Unmarshal(b.buf.Bytes(), &raw); err != nil {
|
||||
var comment *Comment
|
||||
match := yamlLineErrRegex.FindStringSubmatch(err.Error())
|
||||
@@ -2648,13 +2800,14 @@ func (b *metadataParser) Parse() (*Annotations, error) {
|
||||
return nil, augmentYamlError(err, b.comments)
|
||||
}
|
||||
|
||||
var result Annotations
|
||||
result.comments = b.comments
|
||||
result.Scope = raw.Scope
|
||||
result.Entrypoint = raw.Entrypoint
|
||||
result.Title = raw.Title
|
||||
result.Description = raw.Description
|
||||
result.Organizations = raw.Organizations
|
||||
result = &Annotations{
|
||||
comments: b.comments,
|
||||
Scope: raw.Scope,
|
||||
Entrypoint: raw.Entrypoint,
|
||||
Title: raw.Title,
|
||||
Description: raw.Description,
|
||||
Organizations: raw.Organizations,
|
||||
}
|
||||
|
||||
for _, v := range raw.RelatedResources {
|
||||
rr, err := parseRelatedResource(v)
|
||||
@@ -2736,32 +2889,30 @@ func (b *metadataParser) Parse() (*Annotations, error) {
|
||||
result.Authors = append(result.Authors, author)
|
||||
}
|
||||
|
||||
result.Custom = make(map[string]any)
|
||||
for k, v := range raw.Custom {
|
||||
val, err := convertYAMLMapKeyTypes(v, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if raw.Custom != nil {
|
||||
result.Custom = make(map[string]any, len(raw.Custom))
|
||||
for k, v := range raw.Custom {
|
||||
if result.Custom[k], err = convertYAMLMapKeyTypes(v, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
result.Custom[k] = val
|
||||
}
|
||||
|
||||
result.Location = b.loc
|
||||
|
||||
// recreate original text of entire metadata block for location text attribute
|
||||
sb := strings.Builder{}
|
||||
sb.WriteString("# METADATA\n")
|
||||
original := bytes.TrimSuffix(b.buf.Bytes(), newlineBytes)
|
||||
numLines := bytes.Count(original, newlineBytes) + 1
|
||||
preAlloc := len("# METADATA\n") + len(original) + numLines*2 // '# ' prefix added per line
|
||||
|
||||
lines := bytes.Split(b.buf.Bytes(), []byte{'\n'})
|
||||
result.Location.Text = append(make([]byte, 0, preAlloc), "# METADATA\n"...)
|
||||
|
||||
for _, line := range lines[:len(lines)-1] {
|
||||
sb.WriteString("# ")
|
||||
sb.Write(line)
|
||||
sb.WriteByte('\n')
|
||||
for line := range bytes.SplitAfterSeq(original, newlineBytes) {
|
||||
result.Location.Text = append(result.Location.Text, "# "...)
|
||||
result.Location.Text = append(result.Location.Text, line...)
|
||||
}
|
||||
|
||||
result.Location.Text = []byte(strings.TrimSuffix(sb.String(), "\n"))
|
||||
|
||||
return &result, nil
|
||||
return result, err
|
||||
}
|
||||
|
||||
// augmentYamlError augments a YAML error with hints intended to help the user figure out the cause of an otherwise
|
||||
@@ -2770,30 +2921,29 @@ func (b *metadataParser) Parse() (*Annotations, error) {
|
||||
func augmentYamlError(err error, comments []*Comment) error {
|
||||
// Adding hints for when key/value ':' separator isn't suffixed with a legal YAML space symbol
|
||||
for _, comment := range comments {
|
||||
txt := string(comment.Text)
|
||||
parts := strings.Split(txt, ":")
|
||||
if len(parts) > 1 {
|
||||
parts = parts[1:]
|
||||
var invalidSpaces []string
|
||||
for partIndex, part := range parts {
|
||||
if len(part) == 0 && partIndex == len(parts)-1 {
|
||||
invalidSpaces = []string{}
|
||||
break
|
||||
}
|
||||
if bytes.IndexByte(comment.Text, ':') == -1 {
|
||||
continue
|
||||
}
|
||||
parts := bytes.Split(comment.Text, []byte{':'})[1:]
|
||||
|
||||
r, _ := utf8.DecodeRuneInString(part)
|
||||
if r == ' ' || r == '\t' {
|
||||
invalidSpaces = []string{}
|
||||
break
|
||||
}
|
||||
var invalidSpaces []string
|
||||
for partIndex, part := range parts {
|
||||
if len(part) == 0 && partIndex == len(parts)-1 {
|
||||
break
|
||||
}
|
||||
|
||||
invalidSpaces = append(invalidSpaces, fmt.Sprintf("%+q", r))
|
||||
}
|
||||
if len(invalidSpaces) > 0 {
|
||||
err = fmt.Errorf(
|
||||
"%s\n Hint: on line %d, symbol(s) %v immediately following a key/value separator ':' is not a legal yaml space character",
|
||||
err.Error(), comment.Location.Row, invalidSpaces)
|
||||
r, _ := utf8.DecodeRune(part)
|
||||
if r == ' ' || r == '\t' {
|
||||
break
|
||||
}
|
||||
|
||||
invalidSpaces = append(invalidSpaces, fmt.Sprintf("%+q", r))
|
||||
}
|
||||
if len(invalidSpaces) > 0 {
|
||||
err = fmt.Errorf(
|
||||
"%s\n Hint: on line %d, symbol(s) %v immediately following a"+
|
||||
" key/value separator ':' is not a legal yaml space character",
|
||||
err.Error(), comment.Location.Row, invalidSpaces)
|
||||
}
|
||||
}
|
||||
return err
|
||||
@@ -2911,7 +3061,7 @@ func parseAuthorString(s string) (*AuthorAnnotation, error) {
|
||||
if len(trailing) >= len(emailPrefix)+len(emailSuffix) && strings.HasPrefix(trailing, emailPrefix) &&
|
||||
strings.HasSuffix(trailing, emailSuffix) {
|
||||
email = trailing[len(emailPrefix):]
|
||||
email = email[0 : len(email)-len(emailSuffix)]
|
||||
email = email[:len(email)-len(emailSuffix)]
|
||||
namePartCount -= 1
|
||||
}
|
||||
|
||||
@@ -2999,10 +3149,7 @@ func (p *Parser) futureImport(imp *Import, allowedFutureKeywords map[string]toke
|
||||
return
|
||||
}
|
||||
|
||||
kwds := make([]string, 0, len(allowedFutureKeywords))
|
||||
for k := range allowedFutureKeywords {
|
||||
kwds = append(kwds, k)
|
||||
}
|
||||
kwds := util.Keys(allowedFutureKeywords)
|
||||
|
||||
switch len(path) {
|
||||
case 2: // all keywords imported, nothing to do
|
||||
@@ -3052,10 +3199,7 @@ func (p *Parser) regoV1Import(imp *Import) {
|
||||
}
|
||||
|
||||
// import all future keywords with the rego.v1 import
|
||||
kwds := make([]string, 0, len(futureKeywordsV0))
|
||||
for k := range futureKeywordsV0 {
|
||||
kwds = append(kwds, k)
|
||||
}
|
||||
kwds := util.Keys(futureKeywordsV0)
|
||||
|
||||
p.s.s.SetRegoV1Compatible()
|
||||
for _, kw := range kwds {
|
||||
|
||||
+6
-12
@@ -11,7 +11,6 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
@@ -625,10 +624,9 @@ func ParseStatements(filename, input string) ([]Statement, []*Comment, error) {
|
||||
// ParseStatementsWithOpts returns a slice of parsed statements. This is the
|
||||
// default return value from the parser.
|
||||
func ParseStatementsWithOpts(filename, input string, popts ParserOptions) ([]Statement, []*Comment, error) {
|
||||
|
||||
parser := NewParser().
|
||||
WithFilename(filename).
|
||||
WithReader(bytes.NewBufferString(input)).
|
||||
WithReader(strings.NewReader(input)).
|
||||
WithProcessAnnotation(popts.ProcessAnnotation).
|
||||
WithFutureKeywords(popts.FutureKeywords...).
|
||||
WithAllFutureKeywords(popts.AllFutureKeywords).
|
||||
@@ -638,7 +636,6 @@ func ParseStatementsWithOpts(filename, input string, popts ParserOptions) ([]Sta
|
||||
withUnreleasedKeywords(popts.unreleasedKeywords)
|
||||
|
||||
stmts, comments, errs := parser.Parse()
|
||||
|
||||
if len(errs) > 0 {
|
||||
return nil, nil, errs
|
||||
}
|
||||
@@ -647,7 +644,6 @@ func ParseStatementsWithOpts(filename, input string, popts ParserOptions) ([]Sta
|
||||
}
|
||||
|
||||
func parseModule(filename string, stmts []Statement, comments []*Comment, regoCompatibilityMode RegoVersion) (*Module, error) {
|
||||
|
||||
if len(stmts) == 0 {
|
||||
return nil, NewError(ParseErr, &Location{File: filename}, "empty module")
|
||||
}
|
||||
@@ -662,23 +658,21 @@ func parseModule(filename string, stmts []Statement, comments []*Comment, regoCo
|
||||
|
||||
mod := &Module{
|
||||
Package: pkg,
|
||||
stmts: stmts,
|
||||
// The comments slice only holds comments that were not their own statements.
|
||||
Comments: comments,
|
||||
stmts: stmts,
|
||||
}
|
||||
|
||||
// The comments slice only holds comments that were not their own statements.
|
||||
mod.Comments = append(mod.Comments, comments...)
|
||||
|
||||
mod.regoVersion = regoCompatibilityMode
|
||||
if regoCompatibilityMode == RegoUndefined {
|
||||
mod.regoVersion = DefaultRegoVersion
|
||||
} else {
|
||||
mod.regoVersion = regoCompatibilityMode
|
||||
}
|
||||
|
||||
for i, stmt := range stmts[1:] {
|
||||
switch stmt := stmt.(type) {
|
||||
case *Import:
|
||||
mod.Imports = append(mod.Imports, stmt)
|
||||
if mod.regoVersion == RegoV0 && Compare(stmt.Path.Value, RegoV1CompatibleRef) == 0 {
|
||||
if mod.regoVersion == RegoV0 && RegoV1CompatibleRef.Equal(stmt.Path.Value) {
|
||||
mod.regoVersion = RegoV0CompatV1
|
||||
}
|
||||
case *Rule:
|
||||
|
||||
+14
@@ -4,6 +4,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
@@ -83,3 +84,16 @@ func BuiltinNameFromRef(ref Ref) (string, bool) {
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
func AppendDelimeted[T encoding.TextAppender](buf []byte, appenders []T, delim string) ([]byte, error) {
|
||||
for i, item := range appenders {
|
||||
if i > 0 {
|
||||
buf = append(buf, delim...)
|
||||
}
|
||||
var err error
|
||||
if buf, err = item.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
+84
-195
@@ -86,7 +86,11 @@ var ReservedVars = NewVarSet(
|
||||
)
|
||||
|
||||
// Wildcard represents the wildcard variable as defined in the language.
|
||||
var Wildcard = &Term{Value: Var("_")}
|
||||
var (
|
||||
WildcardString = "_"
|
||||
WildcardValue Value = Var(WildcardString)
|
||||
Wildcard = &Term{Value: WildcardValue}
|
||||
)
|
||||
|
||||
// WildcardPrefix is the special character that all wildcard variables are
|
||||
// prefixed with when the statement they are contained in is parsed.
|
||||
@@ -367,40 +371,8 @@ func (mod *Module) Equal(other *Module) bool {
|
||||
}
|
||||
|
||||
func (mod *Module) String() string {
|
||||
byNode := map[Node][]*Annotations{}
|
||||
for _, a := range mod.Annotations {
|
||||
byNode[a.node] = append(byNode[a.node], a)
|
||||
}
|
||||
|
||||
appendAnnotationStrings := func(buf []string, node Node) []string {
|
||||
if as, ok := byNode[node]; ok {
|
||||
for i := range as {
|
||||
buf = append(buf, "# METADATA")
|
||||
buf = append(buf, "# "+as[i].String())
|
||||
}
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
buf := []string{}
|
||||
buf = appendAnnotationStrings(buf, mod.Package)
|
||||
buf = append(buf, mod.Package.String())
|
||||
|
||||
if len(mod.Imports) > 0 {
|
||||
buf = append(buf, "")
|
||||
for _, imp := range mod.Imports {
|
||||
buf = appendAnnotationStrings(buf, imp)
|
||||
buf = append(buf, imp.String())
|
||||
}
|
||||
}
|
||||
if len(mod.Rules) > 0 {
|
||||
buf = append(buf, "")
|
||||
for _, rule := range mod.Rules {
|
||||
buf = appendAnnotationStrings(buf, rule)
|
||||
buf = append(buf, rule.stringWithOpts(toStringOpts{regoVersion: mod.regoVersion}))
|
||||
}
|
||||
}
|
||||
return strings.Join(buf, "\n")
|
||||
buf, _ := mod.AppendText(make([]byte, 0, mod.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
// RuleSet returns a RuleSet containing named rules in the mod.
|
||||
@@ -469,7 +441,8 @@ func (c *Comment) SetLoc(loc *Location) {
|
||||
}
|
||||
|
||||
func (c *Comment) String() string {
|
||||
return "#" + string(c.Text)
|
||||
buf, _ := c.AppendText(make([]byte, 0, c.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
// Copy returns a deep copy of c.
|
||||
@@ -519,16 +492,8 @@ func (pkg *Package) SetLoc(loc *Location) {
|
||||
}
|
||||
|
||||
func (pkg *Package) String() string {
|
||||
if pkg == nil {
|
||||
return "<illegal nil package>"
|
||||
} else if len(pkg.Path) <= 1 {
|
||||
return fmt.Sprintf("package <illegal path %q>", pkg.Path)
|
||||
}
|
||||
// Omit head as all packages have the DefaultRootDocument prepended at parse time.
|
||||
path := make(Ref, len(pkg.Path)-1)
|
||||
path[0] = VarTerm(string(pkg.Path[1].Value.(String)))
|
||||
copy(path[1:], pkg.Path[2:])
|
||||
return fmt.Sprintf("package %v", path)
|
||||
buf, _ := pkg.AppendText(make([]byte, 0, pkg.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
func (pkg *Package) MarshalJSON() ([]byte, error) {
|
||||
@@ -615,7 +580,7 @@ func (imp *Import) SetLoc(loc *Location) {
|
||||
// document. This is the alias if defined otherwise the last element in the
|
||||
// path.
|
||||
func (imp *Import) Name() Var {
|
||||
if len(imp.Alias) != 0 {
|
||||
if imp.Alias != "" {
|
||||
return imp.Alias
|
||||
}
|
||||
switch v := imp.Path.Value.(type) {
|
||||
@@ -631,11 +596,8 @@ func (imp *Import) Name() Var {
|
||||
}
|
||||
|
||||
func (imp *Import) String() string {
|
||||
buf := []string{"import", imp.Path.String()}
|
||||
if len(imp.Alias) > 0 {
|
||||
buf = append(buf, "as", imp.Alias.String())
|
||||
}
|
||||
return strings.Join(buf, " ")
|
||||
buf, _ := imp.AppendText(make([]byte, 0, imp.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
func (imp *Import) MarshalJSON() ([]byte, error) {
|
||||
@@ -726,6 +688,7 @@ func (rule *Rule) SetLoc(loc *Location) {
|
||||
|
||||
// Path returns a ref referring to the document produced by this rule. If rule
|
||||
// is not contained in a module, this function panics.
|
||||
//
|
||||
// Deprecated: Poor handling of ref rules. Use `(*Rule).Ref()` instead.
|
||||
func (rule *Rule) Path() Ref {
|
||||
if rule.Module == nil {
|
||||
@@ -745,11 +708,12 @@ func (rule *Rule) Ref() Ref {
|
||||
}
|
||||
|
||||
func (rule *Rule) String() string {
|
||||
regoVersion := DefaultRegoVersion
|
||||
opts := toStringOpts{}
|
||||
if rule.Module != nil {
|
||||
regoVersion = rule.Module.RegoVersion()
|
||||
opts.regoVersion = rule.Module.RegoVersion()
|
||||
}
|
||||
return rule.stringWithOpts(toStringOpts{regoVersion: regoVersion})
|
||||
buf, _ := rule.appendWithOpts(opts, make([]byte, 0, rule.stringLengthWithOpts(opts)))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
type toStringOpts struct {
|
||||
@@ -763,80 +727,46 @@ func (o toStringOpts) RegoVersion() RegoVersion {
|
||||
return o.regoVersion
|
||||
}
|
||||
|
||||
func (rule *Rule) stringWithOpts(opts toStringOpts) string {
|
||||
buf := []string{}
|
||||
if rule.Default {
|
||||
buf = append(buf, "default")
|
||||
}
|
||||
buf = append(buf, rule.Head.stringWithOpts(opts))
|
||||
if !rule.Default {
|
||||
switch opts.RegoVersion() {
|
||||
case RegoV1, RegoV0CompatV1:
|
||||
buf = append(buf, "if")
|
||||
}
|
||||
buf = append(buf, "{", rule.Body.String(), "}")
|
||||
}
|
||||
if rule.Else != nil {
|
||||
buf = append(buf, rule.Else.elseString(opts))
|
||||
}
|
||||
return strings.Join(buf, " ")
|
||||
}
|
||||
|
||||
func (rule *Rule) isFunction() bool {
|
||||
return len(rule.Head.Args) > 0
|
||||
}
|
||||
|
||||
// ruleJSON is used for JSON serialization of Rule to avoid map allocation overhead.
|
||||
// Field order is alphabetical to match previous map-based output.
|
||||
type ruleJSON struct {
|
||||
Annotations []*Annotations `json:"annotations,omitempty"`
|
||||
Body Body `json:"body"`
|
||||
Default bool `json:"default,omitempty"`
|
||||
Else *Rule `json:"else,omitempty"`
|
||||
Head *Head `json:"head"`
|
||||
Location *Location `json:"location,omitempty"`
|
||||
}
|
||||
|
||||
func (rule *Rule) MarshalJSON() ([]byte, error) {
|
||||
data := map[string]any{
|
||||
"head": rule.Head,
|
||||
"body": rule.Body,
|
||||
data := ruleJSON{
|
||||
Head: rule.Head,
|
||||
Body: rule.Body,
|
||||
}
|
||||
|
||||
if rule.Default {
|
||||
data["default"] = true
|
||||
data.Default = true
|
||||
}
|
||||
|
||||
if rule.Else != nil {
|
||||
data["else"] = rule.Else
|
||||
data.Else = rule.Else
|
||||
}
|
||||
|
||||
if astJSON.GetOptions().MarshalOptions.IncludeLocation.Rule {
|
||||
if rule.Location != nil {
|
||||
data["location"] = rule.Location
|
||||
}
|
||||
data.Location = rule.Location
|
||||
}
|
||||
|
||||
if len(rule.Annotations) != 0 {
|
||||
data["annotations"] = rule.Annotations
|
||||
data.Annotations = rule.Annotations
|
||||
}
|
||||
|
||||
return json.Marshal(data)
|
||||
}
|
||||
|
||||
func (rule *Rule) elseString(opts toStringOpts) string {
|
||||
var buf []string
|
||||
|
||||
buf = append(buf, "else")
|
||||
|
||||
value := rule.Head.Value
|
||||
if value != nil {
|
||||
buf = append(buf, "=", value.String())
|
||||
}
|
||||
|
||||
switch opts.RegoVersion() {
|
||||
case RegoV1, RegoV0CompatV1:
|
||||
buf = append(buf, "if")
|
||||
}
|
||||
|
||||
buf = append(buf, "{", rule.Body.String(), "}")
|
||||
|
||||
if rule.Else != nil {
|
||||
buf = append(buf, rule.Else.elseString(opts))
|
||||
}
|
||||
|
||||
return strings.Join(buf, " ")
|
||||
}
|
||||
|
||||
// NewHead returns a new Head object. If args are provided, the first will be
|
||||
// used for the key and the second will be used for the value.
|
||||
func NewHead(name Var, args ...*Term) *Head {
|
||||
@@ -981,6 +911,7 @@ func (head *Head) Copy() *Head {
|
||||
cpy.Key = head.Key.Copy()
|
||||
cpy.Value = head.Value.Copy()
|
||||
cpy.keywords = nil
|
||||
cpy.Assign = head.Assign
|
||||
return &cpy
|
||||
}
|
||||
|
||||
@@ -994,37 +925,8 @@ func (head *Head) String() string {
|
||||
}
|
||||
|
||||
func (head *Head) stringWithOpts(opts toStringOpts) string {
|
||||
buf := strings.Builder{}
|
||||
buf.WriteString(head.Ref().String())
|
||||
containsAdded := false
|
||||
|
||||
switch {
|
||||
case len(head.Args) != 0:
|
||||
buf.WriteString(head.Args.String())
|
||||
case len(head.Reference) == 1 && head.Key != nil:
|
||||
switch opts.RegoVersion() {
|
||||
case RegoV0:
|
||||
buf.WriteRune('[')
|
||||
buf.WriteString(head.Key.String())
|
||||
buf.WriteRune(']')
|
||||
default:
|
||||
containsAdded = true
|
||||
buf.WriteString(" contains ")
|
||||
buf.WriteString(head.Key.String())
|
||||
}
|
||||
}
|
||||
if head.Value != nil {
|
||||
if head.Assign {
|
||||
buf.WriteString(" := ")
|
||||
} else {
|
||||
buf.WriteString(" = ")
|
||||
}
|
||||
buf.WriteString(head.Value.String())
|
||||
} else if !containsAdded && head.Name == "" && head.Key != nil {
|
||||
buf.WriteString(" contains ")
|
||||
buf.WriteString(head.Key.String())
|
||||
}
|
||||
return buf.String()
|
||||
buf, _ := head.appendWithOpts(opts, make([]byte, 0, head.stringLengthWithOpts(opts)))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
func (head *Head) MarshalJSON() ([]byte, error) {
|
||||
@@ -1087,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())
|
||||
}
|
||||
@@ -1095,11 +997,8 @@ func (a Args) Copy() Args {
|
||||
}
|
||||
|
||||
func (a Args) String() string {
|
||||
buf := make([]string, 0, len(a))
|
||||
for _, t := range a {
|
||||
buf = append(buf, t.String())
|
||||
}
|
||||
return "(" + strings.Join(buf, ", ") + ")"
|
||||
buf, _ := a.AppendText(make([]byte, 0, a.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
// Loc returns the Location of a.
|
||||
@@ -1232,11 +1131,12 @@ func (body Body) SetLoc(loc *Location) {
|
||||
}
|
||||
|
||||
func (body Body) String() string {
|
||||
buf := make([]string, 0, len(body))
|
||||
for _, v := range body {
|
||||
buf = append(buf, v.String())
|
||||
}
|
||||
return strings.Join(buf, "; ")
|
||||
buf, _ := body.AppendText(make([]byte, 0, body.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
func (body Body) AppendText(buf []byte) ([]byte, error) {
|
||||
return AppendDelimeted(buf, body, "; ")
|
||||
}
|
||||
|
||||
// Vars returns a VarSet containing variables in body. The params can be set to
|
||||
@@ -1547,50 +1447,41 @@ func (expr *Expr) SetLoc(loc *Location) {
|
||||
}
|
||||
|
||||
func (expr *Expr) String() string {
|
||||
buf := make([]string, 0, 2+len(expr.With))
|
||||
if expr.Negated {
|
||||
buf = append(buf, "not")
|
||||
}
|
||||
switch t := expr.Terms.(type) {
|
||||
case []*Term:
|
||||
if expr.IsEquality() && validEqAssignArgCount(expr) {
|
||||
buf = append(buf, fmt.Sprintf("%v %v %v", t[1], Equality.Infix, t[2]))
|
||||
} else {
|
||||
buf = append(buf, Call(t).String())
|
||||
}
|
||||
case fmt.Stringer:
|
||||
buf = append(buf, t.String())
|
||||
}
|
||||
buf, _ := expr.AppendText(make([]byte, 0, expr.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
for i := range expr.With {
|
||||
buf = append(buf, expr.With[i].String())
|
||||
}
|
||||
|
||||
return strings.Join(buf, " ")
|
||||
// exprJSON is used for JSON serialization of Expr to avoid map allocation overhead.
|
||||
// Field order is alphabetical to match previous map-based output.
|
||||
type exprJSON struct {
|
||||
Generated bool `json:"generated,omitempty"`
|
||||
Index int `json:"index"`
|
||||
Location *Location `json:"location,omitempty"`
|
||||
Negated bool `json:"negated,omitempty"`
|
||||
Terms any `json:"terms"`
|
||||
With []*With `json:"with,omitempty"`
|
||||
}
|
||||
|
||||
func (expr *Expr) MarshalJSON() ([]byte, error) {
|
||||
data := map[string]any{
|
||||
"terms": expr.Terms,
|
||||
"index": expr.Index,
|
||||
data := exprJSON{
|
||||
Index: expr.Index,
|
||||
Terms: expr.Terms,
|
||||
}
|
||||
|
||||
if len(expr.With) > 0 {
|
||||
data["with"] = expr.With
|
||||
data.With = expr.With
|
||||
}
|
||||
|
||||
if expr.Generated {
|
||||
data["generated"] = true
|
||||
data.Generated = true
|
||||
}
|
||||
|
||||
if expr.Negated {
|
||||
data["negated"] = true
|
||||
data.Negated = true
|
||||
}
|
||||
|
||||
if astJSON.GetOptions().MarshalOptions.IncludeLocation.Expr {
|
||||
if expr.Location != nil {
|
||||
data["location"] = expr.Location
|
||||
}
|
||||
data.Location = expr.Location
|
||||
}
|
||||
|
||||
return json.Marshal(data)
|
||||
@@ -1660,17 +1551,8 @@ func visitCogeneratedExprs(expr *Expr, f func(*Expr) bool) {
|
||||
}
|
||||
|
||||
func (d *SomeDecl) String() string {
|
||||
if call, ok := d.Symbols[0].Value.(Call); ok {
|
||||
if len(call) == 4 {
|
||||
return "some " + call[1].String() + ", " + call[2].String() + " in " + call[3].String()
|
||||
}
|
||||
return "some " + call[1].String() + " in " + call[2].String()
|
||||
}
|
||||
buf := make([]string, len(d.Symbols))
|
||||
for i := range buf {
|
||||
buf[i] = d.Symbols[i].String()
|
||||
}
|
||||
return "some " + strings.Join(buf, ", ")
|
||||
buf, _ := d.AppendText(make([]byte, 0, d.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
// SetLoc sets the Location on d.
|
||||
@@ -1789,7 +1671,8 @@ func (q *Every) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
func (w *With) String() string {
|
||||
return "with " + w.Target.String() + " as " + w.Value.String()
|
||||
buf, _ := w.AppendText(make([]byte, 0, w.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
// Equal returns true if this With is equals the other With.
|
||||
@@ -1846,16 +1729,22 @@ func (w *With) SetLoc(loc *Location) {
|
||||
w.Location = loc
|
||||
}
|
||||
|
||||
// withJSON is used for JSON serialization of With to avoid map allocation overhead.
|
||||
// Field order is alphabetical to match previous map-based output.
|
||||
type withJSON struct {
|
||||
Location *Location `json:"location,omitempty"`
|
||||
Target *Term `json:"target"`
|
||||
Value *Term `json:"value"`
|
||||
}
|
||||
|
||||
func (w *With) MarshalJSON() ([]byte, error) {
|
||||
data := map[string]any{
|
||||
"target": w.Target,
|
||||
"value": w.Value,
|
||||
data := withJSON{
|
||||
Target: w.Target,
|
||||
Value: w.Value,
|
||||
}
|
||||
|
||||
if astJSON.GetOptions().MarshalOptions.IncludeLocation.With {
|
||||
if w.Location != nil {
|
||||
data["location"] = w.Location
|
||||
}
|
||||
data.Location = w.Location
|
||||
}
|
||||
|
||||
return json.Marshal(data)
|
||||
|
||||
+324
@@ -0,0 +1,324 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func (m *Module) AppendText(buf []byte) ([]byte, error) {
|
||||
if m == nil {
|
||||
return append(buf, "<nil module>"...), nil
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
// NOTE(anderseknert): this DOES allocate still, and while that's unfortunate,
|
||||
// we'll be better off dealing with that when we have v2 JSON in the stdlib than
|
||||
// doing manual JSON marshalling (and string length calculations) here.
|
||||
for _, annotations := range m.Annotations {
|
||||
// rule annotations are attached to rules, so only check for package scoped ones here
|
||||
if annotations.Scope == "package" || annotations.Scope == "subpackages" {
|
||||
buf = append(buf, "# METADATA\n# "...)
|
||||
buf = append(buf, annotations.String()...)
|
||||
buf = append(buf, '\n')
|
||||
}
|
||||
}
|
||||
|
||||
if buf, err = m.Package.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, '\n')
|
||||
|
||||
if len(m.Imports) > 0 {
|
||||
for _, imp := range m.Imports {
|
||||
buf = append(buf, '\n')
|
||||
if buf, err = imp.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
buf = append(buf, '\n')
|
||||
}
|
||||
|
||||
if len(m.Rules) > 0 {
|
||||
for _, rule := range m.Rules {
|
||||
buf = append(buf, '\n')
|
||||
if buf, err = rule.appendWithOpts(toStringOpts{regoVersion: m.regoVersion}, buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (pkg *Package) AppendText(buf []byte) ([]byte, error) {
|
||||
var err error
|
||||
if pkg == nil {
|
||||
return append(buf, "<illegal nil package>"...), nil
|
||||
}
|
||||
if len(pkg.Path) <= 1 {
|
||||
buf = append(buf, "package <illegal path \""...)
|
||||
if buf, err = pkg.Path.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(buf, "\">"...), nil
|
||||
}
|
||||
|
||||
buf = append(buf, "package "...)
|
||||
|
||||
path := pkg.Path[1:] // omit "data"
|
||||
|
||||
return path.AppendText(buf)
|
||||
}
|
||||
|
||||
func (imp *Import) AppendText(buf []byte) ([]byte, error) {
|
||||
buf = append(buf, "import "...)
|
||||
var err error
|
||||
if buf, err = imp.Path.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if imp.Alias != "" {
|
||||
buf = append(buf, ' ', 'a', 's', ' ')
|
||||
buf = append(buf, imp.Alias...)
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (r *Rule) AppendText(buf []byte) ([]byte, error) {
|
||||
regoVersion := DefaultRegoVersion
|
||||
if r.Module != nil {
|
||||
regoVersion = r.Module.RegoVersion()
|
||||
}
|
||||
return r.appendWithOpts(toStringOpts{regoVersion: regoVersion}, buf)
|
||||
}
|
||||
|
||||
func (r *Rule) appendWithOpts(opts toStringOpts, buf []byte) ([]byte, error) {
|
||||
// See note in [Module.AppendText] regarding annotations.
|
||||
for _, annotations := range r.Annotations {
|
||||
buf = append(buf, "# METADATA\n# "...)
|
||||
buf = append(buf, annotations.String()...)
|
||||
buf = append(buf, '\n')
|
||||
}
|
||||
|
||||
if r.Default {
|
||||
buf = append(buf, "default "...)
|
||||
}
|
||||
|
||||
var err error
|
||||
if buf, err = r.Head.appendWithOpts(opts, buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !r.Default {
|
||||
switch opts.RegoVersion() {
|
||||
case RegoV1, RegoV0CompatV1:
|
||||
buf = append(buf, " if { "...)
|
||||
default:
|
||||
buf = append(buf, " { "...)
|
||||
}
|
||||
if buf, err = r.Body.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, " }"...)
|
||||
}
|
||||
if r.Else != nil {
|
||||
if buf, err = r.Else.appendElse(opts, buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (r *Rule) appendElse(opts toStringOpts, buf []byte) ([]byte, error) {
|
||||
buf = append(buf, " else "...)
|
||||
|
||||
var err error
|
||||
if r.Head.Value != nil {
|
||||
buf = append(buf, "= "...)
|
||||
if buf, err = r.Head.Value.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if v := opts.RegoVersion(); v == RegoV1 || v == RegoV0CompatV1 {
|
||||
buf = append(buf, " if { "...)
|
||||
} else {
|
||||
buf = append(buf, " { "...)
|
||||
}
|
||||
if buf, err = r.Body.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, " }"...)
|
||||
|
||||
if r.Else != nil {
|
||||
if buf, err = r.Else.appendElse(opts, buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (h *Head) AppendText(buf []byte) ([]byte, error) {
|
||||
return h.appendWithOpts(toStringOpts{}, buf)
|
||||
}
|
||||
|
||||
func (h *Head) appendWithOpts(opts toStringOpts, buf []byte) ([]byte, error) {
|
||||
var err error
|
||||
if h.Reference == nil {
|
||||
buf = append(buf, h.Name...)
|
||||
} else {
|
||||
if buf, err = h.Reference.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
containsAdded := false
|
||||
switch {
|
||||
case len(h.Args) != 0:
|
||||
if buf, err = h.Args.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case len(h.Reference) == 1 && h.Key != nil:
|
||||
switch opts.RegoVersion() {
|
||||
case RegoV0:
|
||||
buf = append(buf, '[')
|
||||
if buf, err = h.Key.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, ']')
|
||||
default:
|
||||
if buf, err = h.Key.AppendText(append(buf, " contains "...)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
containsAdded = true
|
||||
}
|
||||
}
|
||||
if h.Value != nil {
|
||||
if h.Assign {
|
||||
buf = append(buf, " := "...)
|
||||
} else {
|
||||
buf = append(buf, " = "...)
|
||||
}
|
||||
if buf, err = h.Value.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if !containsAdded && h.Name == "" && h.Key != nil {
|
||||
if buf, err = h.Key.AppendText(append(buf, " contains "...)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (a Args) AppendText(buf []byte) ([]byte, error) {
|
||||
var err error
|
||||
buf = append(buf, '(')
|
||||
if buf, err = AppendDelimeted(buf, a, ", "); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(buf, ')'), nil
|
||||
}
|
||||
|
||||
func (expr *Expr) AppendText(buf []byte) ([]byte, error) {
|
||||
if expr.Negated {
|
||||
buf = append(buf, "not "...)
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
switch t := expr.Terms.(type) {
|
||||
case []*Term:
|
||||
if expr.IsEquality() && validEqAssignArgCount(expr) {
|
||||
if buf, err = t[1].AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(append(append(buf, ' '), Equality.Infix...), ' ')
|
||||
if buf, err = t[2].AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if buf, err = Call(t).AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case encoding.TextAppender:
|
||||
if buf, err = t.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported expr terms type: %T", expr.Terms)
|
||||
}
|
||||
|
||||
if len(expr.With) > 0 {
|
||||
buf = append(buf, ' ')
|
||||
}
|
||||
|
||||
return AppendDelimeted(buf, expr.With, " ")
|
||||
}
|
||||
|
||||
func (w *With) AppendText(buf []byte) ([]byte, error) {
|
||||
buf = append(buf, "with "...)
|
||||
var err error
|
||||
if buf, err = w.Target.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, " as "...)
|
||||
if buf, err = w.Value.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (w *Every) AppendText(buf []byte) ([]byte, error) {
|
||||
buf = append(buf, "every "...)
|
||||
var err error
|
||||
if w.Key != nil {
|
||||
if buf, err = w.Key.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, ", "...)
|
||||
}
|
||||
if buf, err = w.Value.AppendText(buf); err == nil {
|
||||
buf = append(buf, " in "...)
|
||||
if buf, err = w.Domain.AppendText(buf); err == nil {
|
||||
buf = append(buf, " { "...)
|
||||
if buf, err = w.Body.AppendText(buf); err == nil {
|
||||
buf = append(buf, " }"...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf, err
|
||||
}
|
||||
|
||||
func (d *SomeDecl) AppendText(buf []byte) ([]byte, error) {
|
||||
var err error
|
||||
buf = append(buf, "some "...)
|
||||
if call, ok := d.Symbols[0].Value.(Call); ok {
|
||||
if buf, err = call[1].AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(call) == 3 {
|
||||
buf = append(buf, " in "...)
|
||||
} else {
|
||||
buf = append(buf, ", "...)
|
||||
}
|
||||
if buf, err = call[2].AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(call) == 4 {
|
||||
buf = append(buf, " in "...)
|
||||
if buf, err = call[3].AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
buf, err = AppendDelimeted(buf, d.Symbols, ", ")
|
||||
|
||||
return buf, err
|
||||
}
|
||||
|
||||
func (c *Comment) AppendText(buf []byte) ([]byte, error) {
|
||||
return append(append(buf, '#'), c.Text...), nil
|
||||
}
|
||||
+12
-15
@@ -27,13 +27,12 @@ func checkRootDocumentOverrides(node any) Errors {
|
||||
errors := Errors{}
|
||||
|
||||
WalkRules(node, func(rule *Rule) bool {
|
||||
var name string
|
||||
name := rule.Head.Name
|
||||
if len(rule.Head.Reference) > 0 {
|
||||
name = rule.Head.Reference[0].Value.(Var).String()
|
||||
} else {
|
||||
name = rule.Head.Name.String()
|
||||
name = rule.Head.Reference[0].Value.(Var)
|
||||
}
|
||||
if RootDocumentRefs.Contains(RefTerm(VarTerm(name))) {
|
||||
|
||||
if ReservedVars.Contains(name) {
|
||||
errors = append(errors, NewError(CompileErr, rule.Location, "rules must not shadow %v (use a different rule name)", name))
|
||||
}
|
||||
|
||||
@@ -52,8 +51,8 @@ func checkRootDocumentOverrides(node any) Errors {
|
||||
if expr.IsAssignment() {
|
||||
// assign() can be called directly, so we need to assert its given first operand exists before checking its name.
|
||||
if nameOp := expr.Operand(0); nameOp != nil {
|
||||
name := nameOp.String()
|
||||
if RootDocumentRefs.Contains(RefTerm(VarTerm(name))) {
|
||||
name := Var(nameOp.String())
|
||||
if ReservedVars.Contains(name) {
|
||||
errors = append(errors, NewError(CompileErr, expr.Location, "variables must not shadow %v (use a different variable name)", name))
|
||||
}
|
||||
}
|
||||
@@ -65,26 +64,24 @@ func checkRootDocumentOverrides(node any) Errors {
|
||||
}
|
||||
|
||||
func walkCalls(node any, f func(any) bool) {
|
||||
vis := &GenericVisitor{func(x any) bool {
|
||||
switch x := x.(type) {
|
||||
vis := NewGenericVisitor(func(x any) bool {
|
||||
switch y := x.(type) {
|
||||
case Call:
|
||||
return f(x)
|
||||
case *Expr:
|
||||
if x.IsCall() {
|
||||
if y.IsCall() {
|
||||
return f(x)
|
||||
}
|
||||
case *Head:
|
||||
// GenericVisitor doesn't walk the rule head ref
|
||||
walkCalls(x.Reference, f)
|
||||
walkCalls(y.Reference, f)
|
||||
}
|
||||
return false
|
||||
}}
|
||||
})
|
||||
vis.Walk(node)
|
||||
}
|
||||
|
||||
func checkDeprecatedBuiltins(deprecatedBuiltinsMap map[string]struct{}, node any) Errors {
|
||||
errs := make(Errors, 0)
|
||||
|
||||
func checkDeprecatedBuiltins(deprecatedBuiltinsMap map[string]struct{}, node any) (errs Errors) {
|
||||
walkCalls(node, func(x any) bool {
|
||||
var operator string
|
||||
var loc *Location
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// Copyright 2026 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ast
|
||||
|
||||
// CountFunc counts the number of items in a slice S that satisfy predicate function f.
|
||||
func CountFunc[T any, S ~[]T](items S, f func(T) bool) (n int) {
|
||||
for i := range items {
|
||||
if f(items[i]) {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
+351
@@ -0,0 +1,351 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// StringLengther is an interface for types that can report their string length without
|
||||
// actually constructing the string. This is useful for pre-allocating buffers, like those
|
||||
// used in AppendText, strings.Builder, bytes.Buffer, etc.
|
||||
type StringLengther interface {
|
||||
StringLength() int
|
||||
}
|
||||
|
||||
// TermSliceStringLength returns the total string length of the given terms, as reported
|
||||
// by the [StringLengther.StringLength] method implementation of each term's [Value]. The
|
||||
// delimLen value will be added between each term's length to account for a delimiter, or
|
||||
// no delimiter if delimLen is 0.
|
||||
// Implementation note: this function is optimized for inlining, and just meets the threshold
|
||||
// for that. Don't change without making sure that's still the case.
|
||||
func TermSliceStringLength(terms []*Term, delimLen int) (n int) {
|
||||
for i := range terms {
|
||||
n += terms[i].StringLength() + delimLen
|
||||
}
|
||||
return max(n-delimLen, 0)
|
||||
}
|
||||
|
||||
func (t *Term) StringLength() int {
|
||||
if sl, ok := t.Value.(StringLengther); ok {
|
||||
return sl.StringLength()
|
||||
}
|
||||
|
||||
panic("expected all ast.Value types to implement StringLenghter interface, got: " + ValueName(t.Value))
|
||||
}
|
||||
|
||||
func (s String) StringLength() int {
|
||||
n := 2 // surrounding quotes
|
||||
bs := util.StringToByteSlice(s)
|
||||
for i := 0; i < len(bs); {
|
||||
r, size := utf8.DecodeRune(bs[i:])
|
||||
switch r {
|
||||
case '\\', '"':
|
||||
n += 2 // escaped backslash or quote
|
||||
case '\b', '\f', '\n', '\r', '\t':
|
||||
n += 2 // escaped control characters
|
||||
default:
|
||||
if r < 0x20 {
|
||||
n += 6 // unicode escape for other control characters
|
||||
} else {
|
||||
n += size // normal rune
|
||||
}
|
||||
}
|
||||
i += size
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (n Number) StringLength() int {
|
||||
return len(n)
|
||||
}
|
||||
|
||||
func (b Boolean) StringLength() int {
|
||||
if b {
|
||||
return 4
|
||||
}
|
||||
return 5
|
||||
}
|
||||
|
||||
func (Null) StringLength() int {
|
||||
return 4
|
||||
}
|
||||
|
||||
func (s *set) StringLength() int {
|
||||
if s.Len() == 0 {
|
||||
return 5 // set()
|
||||
}
|
||||
// surrounding {} + ", " for every element - 1
|
||||
return TermSliceStringLength(s.Slice(), 2) + 2
|
||||
}
|
||||
|
||||
func (a *Array) StringLength() int {
|
||||
if a.Len() == 0 {
|
||||
return 2 // []
|
||||
}
|
||||
// surrounding brackets + ", " for every element - 1
|
||||
return TermSliceStringLength(a.elems, 2) + 2
|
||||
}
|
||||
|
||||
func (o *object) StringLength() (n int) {
|
||||
if o.Len() == 0 {
|
||||
return 2 // {}
|
||||
}
|
||||
// ": " for every item + ", " for every item - 1
|
||||
o.Foreach(func(key, value *Term) {
|
||||
n += key.StringLength() + 4 + value.StringLength() // ": " and ", "
|
||||
})
|
||||
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) {
|
||||
case *Expr:
|
||||
n += 2 + x.StringLength() // for {}
|
||||
case *Term:
|
||||
if s, ok := x.Value.(String); ok {
|
||||
n += len(s) + countUnescapedLeftCurly(string(s))
|
||||
} else {
|
||||
n += x.StringLength()
|
||||
}
|
||||
default:
|
||||
n += 9 // <invalid>
|
||||
}
|
||||
}
|
||||
return n + 3 // $"" or $``
|
||||
}
|
||||
|
||||
func (c Call) StringLength() int {
|
||||
return c[0].StringLength() + 2 + TermSliceStringLength(c[1:], 2)
|
||||
}
|
||||
|
||||
func (r Ref) StringLength() (n int) {
|
||||
rlen := len(r)
|
||||
if rlen == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
if s, ok := r[0].Value.(String); ok {
|
||||
n = len(s) // first term should never be quoted
|
||||
} else {
|
||||
n = r[0].StringLength()
|
||||
}
|
||||
|
||||
if rlen == 1 {
|
||||
return n
|
||||
}
|
||||
|
||||
for _, p := range r[1:] {
|
||||
switch v := p.Value.(type) {
|
||||
case String:
|
||||
str := string(v)
|
||||
if IsVarCompatibleString(str) && !IsKeyword(str) {
|
||||
n += 1 + len(str) // dot + name
|
||||
} else {
|
||||
n += 2 + p.StringLength() // brackets
|
||||
}
|
||||
default:
|
||||
n += 2 + p.StringLength() // brackets
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (v Var) StringLength() int {
|
||||
if v.IsWildcard() {
|
||||
return 1
|
||||
}
|
||||
return len(v)
|
||||
}
|
||||
|
||||
func (s *SetComprehension) StringLength() int {
|
||||
return s.Term.StringLength() + s.Body.StringLength() + 5 // {} and " | "
|
||||
}
|
||||
|
||||
func (a *ArrayComprehension) StringLength() int {
|
||||
return a.Term.StringLength() + a.Body.StringLength() + 5 // [] and " | "
|
||||
}
|
||||
|
||||
func (o *ObjectComprehension) StringLength() (n int) {
|
||||
n += o.Key.StringLength()
|
||||
n += o.Value.StringLength()
|
||||
n += o.Body.StringLength()
|
||||
return n + 7 // "{}"", " | ", and ": "
|
||||
}
|
||||
|
||||
func (m *Module) StringLength() (n int) {
|
||||
if m.Package != nil {
|
||||
n += m.Package.StringLength() + 2 // newlines
|
||||
}
|
||||
|
||||
if len(m.Imports) > 0 {
|
||||
for _, imp := range m.Imports {
|
||||
n += imp.StringLength() + 1 // newline
|
||||
}
|
||||
}
|
||||
|
||||
if len(m.Rules) > 0 {
|
||||
for _, rule := range m.Rules {
|
||||
n += rule.stringLengthWithOpts(toStringOpts{regoVersion: m.regoVersion}) + 1 // newline
|
||||
}
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *Package) StringLength() int {
|
||||
if p == nil {
|
||||
return 21 // <illegal nil package>
|
||||
}
|
||||
if len(p.Path) <= 1 {
|
||||
return 25 + p.Path.StringLength() // // package <illegal path " ... ">
|
||||
}
|
||||
|
||||
return 8 + p.Path[1:].StringLength() // "package ..."
|
||||
}
|
||||
|
||||
func (i *Import) StringLength() (n int) {
|
||||
n = 7 + i.Path.StringLength() // "import " and path
|
||||
if i.Alias != "" {
|
||||
n += 4 + i.Alias.StringLength() // " as " and alias
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (r *Rule) StringLength() int {
|
||||
return r.stringLengthWithOpts(toStringOpts{})
|
||||
}
|
||||
|
||||
func (r *Rule) stringLengthWithOpts(opts toStringOpts) int {
|
||||
n := 0
|
||||
if r.Default {
|
||||
n += 8 // "default "
|
||||
}
|
||||
n += r.Head.stringLengthWithOpts(opts)
|
||||
if !r.Default {
|
||||
switch opts.RegoVersion() {
|
||||
case RegoV1, RegoV0CompatV1:
|
||||
n += 6 // " if { "
|
||||
default:
|
||||
n += 3 // " { "
|
||||
}
|
||||
n += r.Body.StringLength() + 2 // body and closing " }"
|
||||
}
|
||||
if r.Else != nil {
|
||||
n += r.Else.stringLengthWithOpts(opts)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (h *Head) StringLength() int {
|
||||
return h.stringLengthWithOpts(toStringOpts{})
|
||||
}
|
||||
|
||||
func (h *Head) stringLengthWithOpts(opts toStringOpts) int {
|
||||
n := h.Reference.StringLength()
|
||||
containsAdded := false
|
||||
switch {
|
||||
case len(h.Args) != 0:
|
||||
n += h.Args.StringLength()
|
||||
case len(h.Reference) == 1 && h.Key != nil:
|
||||
switch opts.RegoVersion() {
|
||||
case RegoV0:
|
||||
n += 2 + h.Key.StringLength() // for []
|
||||
default:
|
||||
n += 10 + h.Key.StringLength() // " contains "
|
||||
containsAdded = true
|
||||
}
|
||||
}
|
||||
if h.Value != nil {
|
||||
if h.Assign {
|
||||
n += 4 // " := "
|
||||
} else {
|
||||
n += 3 // " = "
|
||||
}
|
||||
n += h.Value.StringLength()
|
||||
} else if !containsAdded && h.Name == "" && h.Key != nil {
|
||||
n += 10 + h.Key.StringLength() // " contains "
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (a Args) StringLength() (n int) {
|
||||
n = 2 // ()
|
||||
for _, t := range a {
|
||||
n += t.StringLength() + 2 // ", "
|
||||
}
|
||||
return n - 2 // minus last ", "
|
||||
}
|
||||
|
||||
func (b Body) StringLength() (n int) {
|
||||
for _, expr := range b {
|
||||
n += expr.StringLength() + 2 // "; "
|
||||
}
|
||||
return max(n-2, 0) // minus last "; " (if `n` isn't 0)
|
||||
}
|
||||
|
||||
func (e *Expr) StringLength() (n int) {
|
||||
if e.Negated {
|
||||
n += 4 // "not "
|
||||
}
|
||||
switch terms := e.Terms.(type) {
|
||||
case []*Term:
|
||||
if e.IsEquality() && validEqAssignArgCount(e) {
|
||||
n += terms[1].StringLength() + len(Equality.Infix) + terms[2].StringLength() + 2 // spaces around =
|
||||
} else {
|
||||
n += Call(terms).StringLength()
|
||||
}
|
||||
case StringLengther:
|
||||
n += terms.StringLength()
|
||||
default:
|
||||
panic(fmt.Sprintf("string length estimation not implemented for type: %T", e.Terms))
|
||||
}
|
||||
|
||||
for _, w := range e.With {
|
||||
n += w.StringLength() + 1 // space before with
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func (w *With) StringLength() int {
|
||||
return w.Target.StringLength() + w.Value.StringLength() + 9 // "with " and " as "
|
||||
}
|
||||
|
||||
func (e *Every) StringLength() int {
|
||||
n := 6 // "every "
|
||||
if e.Key != nil {
|
||||
n += e.Key.StringLength() + 2 // ", "
|
||||
}
|
||||
n += e.Value.StringLength() + 4 // " in "
|
||||
n += e.Domain.StringLength() + 3 // " { "
|
||||
n += e.Body.StringLength() + 2 // " }"
|
||||
return n
|
||||
}
|
||||
|
||||
func (s *SomeDecl) StringLength() int {
|
||||
n := 5 // "some "
|
||||
if call, ok := s.Symbols[0].Value.(Call); ok {
|
||||
n += 4 // " in "
|
||||
n += call[1].StringLength()
|
||||
if len(call) == 4 {
|
||||
n += 2 // ", "
|
||||
}
|
||||
n += call[2].StringLength()
|
||||
if len(call) == 4 {
|
||||
n += call[3].StringLength()
|
||||
}
|
||||
return n
|
||||
}
|
||||
return n + TermSliceStringLength(s.Symbols, 2)
|
||||
}
|
||||
|
||||
func (c *Comment) StringLength() int {
|
||||
return 1 + len(c.Text) // '#' + text
|
||||
}
|
||||
+2
@@ -48,6 +48,8 @@ func ValueName(x Value) string {
|
||||
return "objectcomprehension"
|
||||
case *SetComprehension:
|
||||
return "setcomprehension"
|
||||
case *TemplateString:
|
||||
return "templatestring"
|
||||
}
|
||||
|
||||
return TypeName(x)
|
||||
|
||||
+16
-70
@@ -1,53 +1,31 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"bytes"
|
||||
"sync"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
type termPtrPool struct {
|
||||
pool sync.Pool
|
||||
}
|
||||
var (
|
||||
TermPtrPool = util.NewSyncPool[Term]()
|
||||
BytesReaderPool = util.NewSyncPool[bytes.Reader]()
|
||||
IndexResultPool = util.NewSyncPool[IndexResult]()
|
||||
|
||||
type stringBuilderPool struct {
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
type indexResultPool struct {
|
||||
pool sync.Pool
|
||||
}
|
||||
// Needs custom pool because of custom Put logic.
|
||||
varVisitorPool = &vvPool{
|
||||
pool: sync.Pool{
|
||||
New: func() any {
|
||||
return NewVarVisitor()
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type vvPool struct {
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
func (p *termPtrPool) Get() *Term {
|
||||
return p.pool.Get().(*Term)
|
||||
}
|
||||
|
||||
func (p *termPtrPool) Put(t *Term) {
|
||||
p.pool.Put(t)
|
||||
}
|
||||
|
||||
func (p *stringBuilderPool) Get() *strings.Builder {
|
||||
return p.pool.Get().(*strings.Builder)
|
||||
}
|
||||
|
||||
func (p *stringBuilderPool) Put(sb *strings.Builder) {
|
||||
sb.Reset()
|
||||
p.pool.Put(sb)
|
||||
}
|
||||
|
||||
func (p *indexResultPool) Get() *IndexResult {
|
||||
return p.pool.Get().(*IndexResult)
|
||||
}
|
||||
|
||||
func (p *indexResultPool) Put(x *IndexResult) {
|
||||
if x != nil {
|
||||
p.pool.Put(x)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *vvPool) Get() *VarVisitor {
|
||||
return p.pool.Get().(*VarVisitor)
|
||||
}
|
||||
@@ -58,35 +36,3 @@ func (p *vvPool) Put(vv *VarVisitor) {
|
||||
p.pool.Put(vv)
|
||||
}
|
||||
}
|
||||
|
||||
var TermPtrPool = &termPtrPool{
|
||||
pool: sync.Pool{
|
||||
New: func() any {
|
||||
return &Term{}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var sbPool = &stringBuilderPool{
|
||||
pool: sync.Pool{
|
||||
New: func() any {
|
||||
return &strings.Builder{}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var varVisitorPool = &vvPool{
|
||||
pool: sync.Pool{
|
||||
New: func() any {
|
||||
return NewVarVisitor()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var IndexResultPool = &indexResultPool{
|
||||
pool: sync.Pool{
|
||||
New: func() any {
|
||||
return &IndexResult{}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
+360
-183
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// nolint: deadcode // Public API.
|
||||
package ast
|
||||
|
||||
import (
|
||||
@@ -13,7 +12,6 @@ import (
|
||||
"io"
|
||||
"math"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -26,7 +24,22 @@ import (
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
var errFindNotFound = errors.New("find: not found")
|
||||
// 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{}
|
||||
|
||||
errFindNotFound = errors.New("find: not found")
|
||||
)
|
||||
|
||||
// Location records a position in source code.
|
||||
type Location = location.Location
|
||||
@@ -44,12 +57,15 @@ func NewLocation(text []byte, file string, row int, col int) *Location {
|
||||
// - Variables, References
|
||||
// - Array, Set, and Object Comprehensions
|
||||
// - Calls
|
||||
// - Template Strings
|
||||
type Value interface {
|
||||
Compare(other Value) int // Compare returns <0, 0, or >0 if this Value is less than, equal to, or greater than other, respectively.
|
||||
Find(path Ref) (Value, error) // Find returns value referred to by path or an error if path is not found.
|
||||
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.
|
||||
@@ -352,6 +368,8 @@ func (term *Term) Copy() *Term {
|
||||
cpy.Value = v.Copy()
|
||||
case *SetComprehension:
|
||||
cpy.Value = v.Copy()
|
||||
case *TemplateString:
|
||||
cpy.Value = v.Copy()
|
||||
case Call:
|
||||
cpy.Value = v.Copy()
|
||||
}
|
||||
@@ -405,19 +423,24 @@ func (term *Term) IsGround() bool {
|
||||
return term.Value.IsGround()
|
||||
}
|
||||
|
||||
// termJSON is used to serialize Term to JSON without map allocation.
|
||||
type termJSON struct {
|
||||
Location *Location `json:"location,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Value Value `json:"value"`
|
||||
}
|
||||
|
||||
// MarshalJSON returns the JSON encoding of the term.
|
||||
//
|
||||
// Specialized marshalling logic is required to include a type hint for Value.
|
||||
func (term *Term) MarshalJSON() ([]byte, error) {
|
||||
d := map[string]any{
|
||||
"type": ValueName(term.Value),
|
||||
"value": term.Value,
|
||||
d := termJSON{
|
||||
Type: ValueName(term.Value),
|
||||
Value: term.Value,
|
||||
}
|
||||
jsonOptions := astJSON.GetOptions().MarshalOptions
|
||||
if jsonOptions.IncludeLocation.Term {
|
||||
if term.Location != nil {
|
||||
d["location"] = term.Location
|
||||
}
|
||||
d.Location = term.Location
|
||||
}
|
||||
return json.Marshal(d)
|
||||
}
|
||||
@@ -457,7 +480,17 @@ func (term *Term) Vars() VarSet {
|
||||
}
|
||||
|
||||
// IsConstant returns true if the AST value is constant.
|
||||
// Note that this is only a shallow check as we currently don't have a real
|
||||
// notion of constant "vars" in the AST implementation. Meaning that while we could
|
||||
// derive that a reference to a constant value is also constant, we currently don't.
|
||||
func IsConstant(v Value) bool {
|
||||
switch v.(type) {
|
||||
case Null, Boolean, Number, String:
|
||||
return true
|
||||
case Var, Ref, *ArrayComprehension, *ObjectComprehension, *SetComprehension, Call:
|
||||
return false
|
||||
}
|
||||
|
||||
found := false
|
||||
vis := GenericVisitor{
|
||||
func(x any) bool {
|
||||
@@ -532,8 +565,6 @@ func IsScalar(v Value) bool {
|
||||
// Null represents the null value defined by JSON.
|
||||
type Null struct{}
|
||||
|
||||
var NullValue Value = Null{}
|
||||
|
||||
// NullTerm creates a new Term with a Null value.
|
||||
func NullTerm() *Term {
|
||||
return &Term{Value: NullValue}
|
||||
@@ -651,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.
|
||||
@@ -819,12 +851,159 @@ func (str String) Hash() int {
|
||||
return int(xxhash.Sum64String(string(str)))
|
||||
}
|
||||
|
||||
type TemplateString struct {
|
||||
Parts []Node `json:"parts"`
|
||||
MultiLine bool `json:"multi_line"`
|
||||
}
|
||||
|
||||
func (ts *TemplateString) Copy() *TemplateString {
|
||||
cpy := &TemplateString{MultiLine: ts.MultiLine, Parts: make([]Node, len(ts.Parts))}
|
||||
for i, p := range ts.Parts {
|
||||
switch v := p.(type) {
|
||||
case *Expr:
|
||||
cpy.Parts[i] = v.Copy()
|
||||
case *Term:
|
||||
cpy.Parts[i] = v.Copy()
|
||||
}
|
||||
}
|
||||
return cpy
|
||||
}
|
||||
|
||||
func (ts *TemplateString) Equal(other Value) bool {
|
||||
if o, ok := other.(*TemplateString); ok && ts.MultiLine == o.MultiLine && len(ts.Parts) == len(o.Parts) {
|
||||
for i, p := range ts.Parts {
|
||||
switch v := p.(type) {
|
||||
case *Expr:
|
||||
if ope, ok := o.Parts[i].(*Expr); !ok || !v.Equal(ope) {
|
||||
return false
|
||||
}
|
||||
case *Term:
|
||||
if opt, ok := o.Parts[i].(*Term); !ok || !v.Equal(opt) {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (ts *TemplateString) Compare(other Value) int {
|
||||
if ots, ok := other.(*TemplateString); ok {
|
||||
if ts.MultiLine != ots.MultiLine {
|
||||
if !ts.MultiLine {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
if len(ts.Parts) != len(ots.Parts) {
|
||||
return len(ts.Parts) - len(ots.Parts)
|
||||
}
|
||||
|
||||
for i := range ts.Parts {
|
||||
if cmp := Compare(ts.Parts[i], ots.Parts[i]); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
return Compare(ts, other)
|
||||
}
|
||||
|
||||
func (ts *TemplateString) Find(path Ref) (Value, error) {
|
||||
if len(path) == 0 {
|
||||
return ts, nil
|
||||
}
|
||||
return nil, errFindNotFound
|
||||
}
|
||||
|
||||
func (ts *TemplateString) Hash() int {
|
||||
hash := 0
|
||||
for _, p := range ts.Parts {
|
||||
switch x := p.(type) {
|
||||
case *Expr:
|
||||
hash += x.Hash()
|
||||
case *Term:
|
||||
hash += x.Value.Hash()
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid template part type %T", p))
|
||||
}
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
func (*TemplateString) IsGround() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (ts *TemplateString) String() string {
|
||||
buf, _ := ts.AppendText(make([]byte, 0, ts.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
func TemplateStringTerm(multiLine bool, parts ...Node) *Term {
|
||||
return &Term{Value: &TemplateString{MultiLine: multiLine, Parts: parts}}
|
||||
}
|
||||
|
||||
// EscapeTemplateStringStringPart escapes unescaped left curly braces in s - i.e "{" becomes "\{".
|
||||
// The internal representation of string terms within a template string does **NOT**
|
||||
// treat '{' as special, but expects code dealing with template strings to escape them when
|
||||
// required, such as when serializing the complete template string. Code that programmatically
|
||||
// constructs template strings should not pre-escape left curly braces in string term parts.
|
||||
//
|
||||
// // TODO(anders): a future optimization would be to combine this with the other escaping done
|
||||
// // for strings (e.g. escaping quotes, backslashes, and JSON control characters) in a single operation
|
||||
// // to avoid multiple passes and allocations over the same string. That's currently done by
|
||||
// // strconv.Quote, so we would need to re-implement that logic in code of our own.
|
||||
// // NOTE(anders): I would love to come up with a better name for this component than
|
||||
// // "TemplateStringStringPart"..
|
||||
func EscapeTemplateStringStringPart(s string) string {
|
||||
numUnescaped := countUnescapedLeftCurly(s)
|
||||
if numUnescaped == 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
return util.ByteSliceToString(AppendEscapedTemplateStringStringPart(make([]byte, 0, len(s)+numUnescaped), s))
|
||||
}
|
||||
|
||||
func AppendEscapedTemplateStringStringPart(buf []byte, s string) []byte {
|
||||
if s[0] == '{' {
|
||||
buf = append(buf, '\\', s[0])
|
||||
} else {
|
||||
buf = append(buf, s[0])
|
||||
}
|
||||
|
||||
for i := 1; i < len(s); i++ {
|
||||
if s[i] == '{' && s[i-1] != '\\' {
|
||||
buf = append(buf, '\\', s[i])
|
||||
} else {
|
||||
buf = append(buf, s[i])
|
||||
}
|
||||
}
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
func countUnescapedLeftCurly(s string) (n int) {
|
||||
// Note(anders): while not the functions I'd intuitively reach for to solve this,
|
||||
// they are hands down the fastest option here, as they're done in assembly, which
|
||||
// performs about an order of magnitude better than a manual loop in Go.
|
||||
if n = strings.Count(s, "{"); n > 0 {
|
||||
n -= strings.Count(s, `\{`)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Var represents a variable as defined by the language.
|
||||
type Var string
|
||||
|
||||
// VarTerm creates a new Term with a Variable value.
|
||||
func VarTerm(v string) *Term {
|
||||
return &Term{Value: Var(v)}
|
||||
return &Term{Value: InternedVarValue(v)}
|
||||
}
|
||||
|
||||
// Equal returns true if the other Value is a Variable and has the same value
|
||||
@@ -881,7 +1060,7 @@ func (v Var) String() string {
|
||||
// illegal variable name character (WildcardPrefix) to avoid conflicts. When
|
||||
// we serialize the variable here, we need to make sure it's parseable.
|
||||
if v.IsWildcard() {
|
||||
return Wildcard.String()
|
||||
return WildcardString
|
||||
}
|
||||
return string(v)
|
||||
}
|
||||
@@ -952,14 +1131,14 @@ func (ref Ref) Insert(x *Term, pos int) Ref {
|
||||
// Extend returns a copy of ref with the terms from other appended. The head of
|
||||
// other will be converted to a string.
|
||||
func (ref Ref) Extend(other Ref) Ref {
|
||||
dst := make(Ref, len(ref)+len(other))
|
||||
offset := len(ref)
|
||||
dst := make(Ref, offset+len(other))
|
||||
copy(dst, ref)
|
||||
|
||||
head := other[0].Copy()
|
||||
head.Value = String(head.Value.(Var))
|
||||
offset := len(ref)
|
||||
dst[offset] = head
|
||||
|
||||
dst[offset] = head
|
||||
copy(dst[offset+1:], other[1:])
|
||||
return dst
|
||||
}
|
||||
@@ -1071,42 +1250,38 @@ func (ref Ref) HasPrefix(other Ref) bool {
|
||||
func (ref Ref) ConstantPrefix() Ref {
|
||||
i := ref.Dynamic()
|
||||
if i < 0 {
|
||||
return ref.Copy()
|
||||
return ref
|
||||
}
|
||||
return ref[:i].Copy()
|
||||
return ref[:i]
|
||||
}
|
||||
|
||||
// StringPrefix returns the string portion of the ref starting from the head.
|
||||
func (ref Ref) StringPrefix() Ref {
|
||||
for i := 1; i < len(ref); i++ {
|
||||
switch ref[i].Value.(type) {
|
||||
case String: // pass
|
||||
default: // cut off
|
||||
return ref[:i].Copy()
|
||||
return ref[:i]
|
||||
}
|
||||
}
|
||||
|
||||
return ref.Copy()
|
||||
return ref
|
||||
}
|
||||
|
||||
// GroundPrefix returns the ground portion of the ref starting from the head. By
|
||||
// definition, the head of the reference is always ground.
|
||||
func (ref Ref) GroundPrefix() Ref {
|
||||
if ref.IsGround() {
|
||||
return ref
|
||||
}
|
||||
|
||||
prefix := make(Ref, 0, len(ref))
|
||||
|
||||
for i, x := range ref {
|
||||
if i > 0 && !x.IsGround() {
|
||||
break
|
||||
for i := range ref {
|
||||
if i > 0 && !ref[i].IsGround() {
|
||||
return ref[:i]
|
||||
}
|
||||
prefix = append(prefix, x)
|
||||
}
|
||||
|
||||
return prefix
|
||||
return ref
|
||||
}
|
||||
|
||||
// DynamicSuffix returns the dynamic portion of the ref.
|
||||
// If the ref is not dynamic, nil is returned.
|
||||
func (ref Ref) DynamicSuffix() Ref {
|
||||
i := ref.Dynamic()
|
||||
if i < 0 {
|
||||
@@ -1117,7 +1292,7 @@ func (ref Ref) DynamicSuffix() Ref {
|
||||
|
||||
// IsGround returns true if all of the parts of the Ref are ground.
|
||||
func (ref Ref) IsGround() bool {
|
||||
if len(ref) == 0 {
|
||||
if len(ref) < 2 {
|
||||
return true
|
||||
}
|
||||
return termSliceIsGround(ref[1:])
|
||||
@@ -1137,85 +1312,84 @@ func (ref Ref) IsNested() bool {
|
||||
// contains non-string terms this function returns an error. Path
|
||||
// components are escaped.
|
||||
func (ref Ref) Ptr() (string, error) {
|
||||
parts := make([]string, 0, len(ref)-1)
|
||||
for _, term := range ref[1:] {
|
||||
if str, ok := term.Value.(String); ok {
|
||||
parts = append(parts, url.PathEscape(string(str)))
|
||||
} else {
|
||||
buf := &strings.Builder{}
|
||||
tail := ref[1:]
|
||||
|
||||
l := max(len(tail)-1, 0) // number of '/' to add
|
||||
for i := range tail {
|
||||
str, ok := tail[i].Value.(String)
|
||||
if !ok {
|
||||
return "", errors.New("invalid path value type")
|
||||
}
|
||||
l += len(str)
|
||||
}
|
||||
return strings.Join(parts, "/"), nil
|
||||
buf.Grow(l)
|
||||
|
||||
for i := range tail {
|
||||
if i > 0 {
|
||||
buf.WriteByte('/')
|
||||
}
|
||||
str := string(tail[i].Value.(String))
|
||||
// Sadly, the url package does not expose an appender for this.
|
||||
buf.WriteString(url.PathEscape(str))
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
var varRegexp = regexp.MustCompile("^[[:alpha:]_][[:alpha:][:digit:]_]*$")
|
||||
|
||||
// IsVarCompatibleString returns true if s is a valid variable name. String s is a valid variable
|
||||
// name if it starts with a letter (a-z or A-Z) or underscore (_) and is followed by
|
||||
// letters (a-z or A-Z), digits (0-9), and underscores.
|
||||
func IsVarCompatibleString(s string) bool {
|
||||
return varRegexp.MatchString(s)
|
||||
}
|
||||
l := len(s)
|
||||
if l == 0 {
|
||||
return false
|
||||
}
|
||||
// not exactly easy on the eyes, but often orders of magnitude faster
|
||||
// than using a compiled regex (see benchmarks in term_bench_test.go)
|
||||
is_letter := func(c byte) bool {
|
||||
return (c > 96 && c < 123) || (c > 64 && c < 91)
|
||||
}
|
||||
is_digit := func(c byte) bool {
|
||||
return c > 47 && c < 58
|
||||
}
|
||||
|
||||
var bbPool = &sync.Pool{
|
||||
New: func() any {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
// first character must be a letter or underscore
|
||||
c := s[0]
|
||||
if !(is_letter(c) || c == 95) {
|
||||
return false
|
||||
}
|
||||
|
||||
// remaining characters must be letters, digits, or underscores
|
||||
for i := 1; i < l; i++ {
|
||||
if c = s[i]; !(is_letter(c) || is_digit(c) || c == 95) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (ref Ref) String() string {
|
||||
// Note(anderseknert):
|
||||
// Options tried in the order of cheapness, where after some effort,
|
||||
// only the last option now requires a (single) allocation:
|
||||
// 1. empty ref
|
||||
// 2. single var ref
|
||||
// 3. built-in function ref
|
||||
// 4. concatenated parts
|
||||
reflen := len(ref)
|
||||
if reflen == 0 {
|
||||
l := len(ref)
|
||||
// First check for zero-alloc options, as making the buffer for AppendText
|
||||
// always costs an allocation.
|
||||
if l == 0 {
|
||||
return ""
|
||||
}
|
||||
if reflen == 1 {
|
||||
if l == 1 {
|
||||
if s, ok := ref[0].Value.(String); ok {
|
||||
// Ref head should normally be a Var, but if for some reason
|
||||
// it's a string, don't quote it.
|
||||
return string(s)
|
||||
}
|
||||
return ref[0].Value.String()
|
||||
}
|
||||
if name, ok := BuiltinNameFromRef(ref); ok {
|
||||
return name
|
||||
}
|
||||
|
||||
_var := ref[0].Value.String()
|
||||
|
||||
bb := bbPool.Get().(*bytes.Buffer)
|
||||
bb.Reset()
|
||||
|
||||
defer bbPool.Put(bb)
|
||||
|
||||
bb.Grow(len(_var) + len(ref[1:])*7) // rough estimate
|
||||
bb.WriteString(_var)
|
||||
|
||||
for _, p := range ref[1:] {
|
||||
switch p := p.Value.(type) {
|
||||
case String:
|
||||
str := string(p)
|
||||
if IsVarCompatibleString(str) && !IsKeyword(str) {
|
||||
bb.WriteByte('.')
|
||||
bb.WriteString(str)
|
||||
} else {
|
||||
bb.WriteByte('[')
|
||||
// Determine whether we need the full JSON-escaped form
|
||||
if strings.ContainsFunc(str, isControlOrBackslash) {
|
||||
bb.Write(strconv.AppendQuote(bb.AvailableBuffer(), str))
|
||||
} else {
|
||||
bb.WriteByte('"')
|
||||
bb.WriteString(str)
|
||||
bb.WriteByte('"')
|
||||
}
|
||||
bb.WriteByte(']')
|
||||
}
|
||||
default:
|
||||
bb.WriteByte('[')
|
||||
bb.WriteString(p.String())
|
||||
bb.WriteByte(']')
|
||||
}
|
||||
}
|
||||
|
||||
return bb.String()
|
||||
buf, _ := ref.AppendText(make([]byte, 0, ref.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
// OutputVars returns a VarSet containing variables that would be bound by evaluating
|
||||
@@ -1258,6 +1432,15 @@ func NewArray(a ...*Term) *Array {
|
||||
return arr
|
||||
}
|
||||
|
||||
// NewArrayWithCapacity returns a new empty Array with the given capacity pre-allocated.
|
||||
func NewArrayWithCapacity(capacity int) *Array {
|
||||
return &Array{
|
||||
elems: make([]*Term, 0, capacity),
|
||||
hashs: make([]int, 0, capacity),
|
||||
ground: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Array represents an array as defined by the language. Arrays are similar to the
|
||||
// same types as defined by JSON with the exception that they can contain Vars
|
||||
// and References.
|
||||
@@ -1270,13 +1453,12 @@ type Array struct {
|
||||
|
||||
// Copy returns a deep copy of arr.
|
||||
func (arr *Array) Copy() *Array {
|
||||
cpy := make([]int, len(arr.elems))
|
||||
copy(cpy, arr.hashs)
|
||||
return &Array{
|
||||
elems: termSliceCopy(arr.elems),
|
||||
hashs: cpy,
|
||||
hashs: slices.Clone(arr.hashs),
|
||||
hash: arr.hash,
|
||||
ground: arr.IsGround()}
|
||||
ground: arr.ground,
|
||||
}
|
||||
}
|
||||
|
||||
// Equal returns true if arr is equal to other.
|
||||
@@ -1387,21 +1569,8 @@ func (arr *Array) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
func (arr *Array) String() string {
|
||||
sb := sbPool.Get()
|
||||
sb.Grow(len(arr.elems) * 16)
|
||||
|
||||
defer sbPool.Put(sb)
|
||||
|
||||
sb.WriteByte('[')
|
||||
for i, e := range arr.elems {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString(e.String())
|
||||
}
|
||||
sb.WriteByte(']')
|
||||
|
||||
return sb.String()
|
||||
buf, _ := arr.AppendText(make([]byte, 0, arr.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
// Len returns the number of elements in the array.
|
||||
@@ -1519,6 +1688,11 @@ func NewSet(t ...*Term) Set {
|
||||
return s
|
||||
}
|
||||
|
||||
// NewSetWithCapacity returns a new empty Set with the given capacity pre-allocated.
|
||||
func NewSetWithCapacity(capacity int) Set {
|
||||
return newset(capacity)
|
||||
}
|
||||
|
||||
func newset(n int) *set {
|
||||
var keys []*Term
|
||||
if n > 0 {
|
||||
@@ -1555,13 +1729,19 @@ type set struct {
|
||||
|
||||
// Copy returns a deep copy of s.
|
||||
func (s *set) Copy() Set {
|
||||
terms := make([]*Term, len(s.keys))
|
||||
for i := range s.keys {
|
||||
terms[i] = s.keys[i].Copy()
|
||||
cpy := &set{
|
||||
hash: s.hash,
|
||||
ground: s.ground,
|
||||
sortGuard: sync.Once{},
|
||||
elems: make(map[int]*Term, len(s.elems)),
|
||||
keys: make([]*Term, 0, len(s.keys)),
|
||||
}
|
||||
cpy := NewSet(terms...).(*set)
|
||||
cpy.hash = s.hash
|
||||
cpy.ground = s.ground
|
||||
|
||||
for hash := range s.elems {
|
||||
cpy.elems[hash] = s.elems[hash].Copy()
|
||||
cpy.keys = append(cpy.keys, cpy.elems[hash])
|
||||
}
|
||||
|
||||
return cpy
|
||||
}
|
||||
|
||||
@@ -1576,25 +1756,8 @@ func (s *set) Hash() int {
|
||||
}
|
||||
|
||||
func (s *set) String() string {
|
||||
if s.Len() == 0 {
|
||||
return "set()"
|
||||
}
|
||||
|
||||
sb := sbPool.Get()
|
||||
sb.Grow(s.Len() * 16)
|
||||
|
||||
defer sbPool.Put(sb)
|
||||
|
||||
sb.WriteByte('{')
|
||||
for i := range s.sortedKeys() {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString(s.keys[i].Value.String())
|
||||
}
|
||||
sb.WriteByte('}')
|
||||
|
||||
return sb.String()
|
||||
buf, _ := s.AppendText(make([]byte, 0, s.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
func (s *set) sortedKeys() []*Term {
|
||||
@@ -1635,14 +1798,14 @@ func (s *set) Diff(other Set) Set {
|
||||
return NewSet()
|
||||
}
|
||||
|
||||
terms := make([]*Term, 0, len(s.keys))
|
||||
for _, term := range s.sortedKeys() {
|
||||
result := newset(len(s.keys))
|
||||
for _, term := range s.keys {
|
||||
if !other.Contains(term) {
|
||||
terms = append(terms, term)
|
||||
result.insert(term, false)
|
||||
}
|
||||
}
|
||||
|
||||
return NewSet(terms...)
|
||||
return result
|
||||
}
|
||||
|
||||
// Intersect returns the set containing elements in both s and other.
|
||||
@@ -1657,21 +1820,28 @@ func (s *set) Intersect(other Set) Set {
|
||||
n = m
|
||||
}
|
||||
|
||||
terms := make([]*Term, 0, n)
|
||||
for _, term := range ss.sortedKeys() {
|
||||
result := newset(n)
|
||||
for _, term := range ss.keys {
|
||||
if so.Contains(term) {
|
||||
terms = append(terms, term)
|
||||
result.insert(term, false)
|
||||
}
|
||||
}
|
||||
|
||||
return NewSet(terms...)
|
||||
return result
|
||||
}
|
||||
|
||||
// Union returns the set containing all elements of s and other.
|
||||
func (s *set) Union(other Set) Set {
|
||||
r := NewSet()
|
||||
s.Foreach(r.Add)
|
||||
other.Foreach(r.Add)
|
||||
o := other.(*set)
|
||||
// Pre-allocate with max size - avoids over-allocation for overlapping sets
|
||||
// while only requiring one potential grow for disjoint sets.
|
||||
r := newset(max(len(s.keys), len(o.keys)))
|
||||
for _, term := range s.keys {
|
||||
r.insert(term, false)
|
||||
}
|
||||
for _, term := range o.keys {
|
||||
r.insert(term, false)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -1845,6 +2015,11 @@ func NewObject(t ...[2]*Term) Object {
|
||||
return obj
|
||||
}
|
||||
|
||||
// NewObjectWithCapacity returns a new empty Object with the given capacity pre-allocated.
|
||||
func NewObjectWithCapacity(capacity int) Object {
|
||||
return newobject(capacity)
|
||||
}
|
||||
|
||||
// ObjectTerm creates a new Term with an Object value.
|
||||
func ObjectTerm(o ...[2]*Term) *Term {
|
||||
return &Term{Value: NewObject(o...)}
|
||||
@@ -2316,19 +2491,21 @@ func (obj *object) Merge(other Object) (Object, bool) {
|
||||
// is called. The conflictResolver can return a merged value and a boolean
|
||||
// indicating if the merge has failed and should stop.
|
||||
func (obj *object) MergeWith(other Object, conflictResolver func(v1, v2 *Term) (*Term, bool)) (Object, bool) {
|
||||
result := NewObject()
|
||||
// Might overallocate assuming no conflicts is the common case,
|
||||
// but that's typically faster than iterating over each object twice.
|
||||
result := newobject(obj.Len() + other.Len())
|
||||
stop := obj.Until(func(k, v *Term) bool {
|
||||
v2 := other.Get(k)
|
||||
// The key didn't exist in other, keep the original value
|
||||
if v2 == nil {
|
||||
result.Insert(k, v)
|
||||
result.insert(k, v, false)
|
||||
return false
|
||||
}
|
||||
|
||||
// The key exists in both, resolve the conflict if possible
|
||||
merged, stop := conflictResolver(v, v2)
|
||||
if !stop {
|
||||
result.Insert(k, merged)
|
||||
result.insert(k, merged, false)
|
||||
}
|
||||
return stop
|
||||
})
|
||||
@@ -2340,7 +2517,7 @@ func (obj *object) MergeWith(other Object, conflictResolver func(v1, v2 *Term) (
|
||||
// Copy in any values from other for keys that don't exist in obj
|
||||
other.Foreach(func(k, v *Term) {
|
||||
if v2 := obj.Get(k); v2 == nil {
|
||||
result.Insert(k, v)
|
||||
result.insert(k, v, false)
|
||||
}
|
||||
})
|
||||
return result, true
|
||||
@@ -2363,24 +2540,8 @@ func (obj *object) Len() int {
|
||||
}
|
||||
|
||||
func (obj *object) String() string {
|
||||
sb := sbPool.Get()
|
||||
sb.Grow(obj.Len() * 32)
|
||||
|
||||
defer sbPool.Put(sb)
|
||||
|
||||
sb.WriteByte('{')
|
||||
|
||||
for i, elem := range obj.sortedKeys() {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString(elem.key.String())
|
||||
sb.WriteString(": ")
|
||||
sb.WriteString(elem.value.String())
|
||||
}
|
||||
sb.WriteByte('}')
|
||||
|
||||
return sb.String()
|
||||
buf, _ := obj.AppendText(make([]byte, 0, obj.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
func (*object) get(*Term) *objectElem {
|
||||
@@ -2451,7 +2612,7 @@ func filterObject(o Value, filter Value) (Value, error) {
|
||||
case String, Number, Boolean, Null:
|
||||
return o, nil
|
||||
case *Array:
|
||||
values := NewArray()
|
||||
values := make([]*Term, 0, v.Len())
|
||||
for i := range v.Len() {
|
||||
subFilter := filteredObj.Get(InternedIntegerString(i))
|
||||
if subFilter != nil {
|
||||
@@ -2459,10 +2620,10 @@ func filterObject(o Value, filter Value) (Value, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
values = values.Append(NewTerm(filteredValue))
|
||||
values = append(values, NewTerm(filteredValue))
|
||||
}
|
||||
}
|
||||
return values, nil
|
||||
return NewArray(values...), nil
|
||||
case Set:
|
||||
terms := make([]*Term, 0, v.Len())
|
||||
for _, t := range v.Slice() {
|
||||
@@ -2585,7 +2746,8 @@ func (ac *ArrayComprehension) IsGround() bool {
|
||||
}
|
||||
|
||||
func (ac *ArrayComprehension) String() string {
|
||||
return "[" + ac.Term.String() + " | " + ac.Body.String() + "]"
|
||||
buf, _ := ac.AppendText(make([]byte, 0, ac.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
// ObjectComprehension represents an object comprehension as defined in the language.
|
||||
@@ -2645,7 +2807,8 @@ func (oc *ObjectComprehension) IsGround() bool {
|
||||
}
|
||||
|
||||
func (oc *ObjectComprehension) String() string {
|
||||
return "{" + oc.Key.String() + ": " + oc.Value.String() + " | " + oc.Body.String() + "}"
|
||||
buf, _ := oc.AppendText(make([]byte, 0, oc.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
// SetComprehension represents a set comprehension as defined in the language.
|
||||
@@ -2702,7 +2865,8 @@ func (sc *SetComprehension) IsGround() bool {
|
||||
}
|
||||
|
||||
func (sc *SetComprehension) String() string {
|
||||
return "{" + sc.Term.String() + " | " + sc.Body.String() + "}"
|
||||
buf, _ := sc.AppendText(make([]byte, 0, sc.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
// Call represents as function call in the language.
|
||||
@@ -2740,18 +2904,31 @@ func (c Call) IsGround() bool {
|
||||
return termSliceIsGround(c)
|
||||
}
|
||||
|
||||
// MakeExpr returns an ew Expr from this call.
|
||||
// MakeExpr returns a new Expr from this call.
|
||||
func (c Call) MakeExpr(output *Term) *Expr {
|
||||
terms := []*Term(c)
|
||||
return NewExpr(append(terms, output))
|
||||
}
|
||||
|
||||
func (c Call) String() string {
|
||||
args := make([]string, len(c)-1)
|
||||
for i := 1; i < len(c); i++ {
|
||||
args[i-1] = c[i].String()
|
||||
func (c Call) Operator() Ref {
|
||||
if len(c) == 0 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Sprintf("%v(%v)", c[0], strings.Join(args, ", "))
|
||||
|
||||
return c[0].Value.(Ref)
|
||||
}
|
||||
|
||||
func (c Call) Operands() []*Term {
|
||||
if len(c) < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return c[1:]
|
||||
}
|
||||
|
||||
func (c Call) String() string {
|
||||
buf, _ := c.AppendText(make([]byte, 0, c.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
func termSliceCopy(a []*Term) []*Term {
|
||||
|
||||
+266
@@ -0,0 +1,266 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// AppendText appends the text representation of term (i.e. as printed in policy) to
|
||||
// buf and returns the extended buffer.
|
||||
func (term *Term) AppendText(buf []byte) ([]byte, error) {
|
||||
if app, ok := term.Value.(encoding.TextAppender); ok {
|
||||
return app.AppendText(buf)
|
||||
}
|
||||
|
||||
return append(buf, term.Value.String()...), nil
|
||||
}
|
||||
|
||||
func (v Var) AppendText(buf []byte) ([]byte, error) {
|
||||
if v.IsWildcard() {
|
||||
return append(buf, WildcardString...), nil
|
||||
}
|
||||
return append(buf, v...), nil
|
||||
}
|
||||
|
||||
func (b Boolean) AppendText(buf []byte) ([]byte, error) {
|
||||
if b {
|
||||
return append(buf, "true"...), nil
|
||||
}
|
||||
return append(buf, "false"...), nil
|
||||
}
|
||||
|
||||
func (Null) AppendText(buf []byte) ([]byte, error) {
|
||||
return append(buf, "null"...), nil
|
||||
}
|
||||
|
||||
func (str String) AppendText(buf []byte) ([]byte, error) {
|
||||
return strconv.AppendQuote(buf, string(str)), nil
|
||||
}
|
||||
|
||||
func (str String) appendNoQuote(buf []byte) []byte {
|
||||
// Append using strconv.AppendQuote for proper escaping, but trim off
|
||||
// the leading and trailing quotes afterwards.
|
||||
oldLen := len(buf)
|
||||
buf = strconv.AppendQuote(buf, string(str))
|
||||
newLen := len(buf)
|
||||
quoted := buf[oldLen:newLen]
|
||||
|
||||
return append(buf[:oldLen], quoted[1:len(quoted)-1]...)
|
||||
}
|
||||
|
||||
func (num Number) AppendText(buf []byte) ([]byte, error) {
|
||||
return append(buf, num...), nil
|
||||
}
|
||||
|
||||
func (arr *Array) AppendText(buf []byte) ([]byte, error) {
|
||||
buf, err := AppendDelimeted(append(buf, '['), arr.elems, ", ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(buf, ']'), nil
|
||||
}
|
||||
|
||||
func (obj *object) AppendText(buf []byte) ([]byte, error) {
|
||||
olen := obj.Len()
|
||||
if olen == 0 {
|
||||
return append(buf, "{}"...), nil
|
||||
}
|
||||
|
||||
buf = append(buf, '{')
|
||||
|
||||
var err error
|
||||
|
||||
// first key-value pair
|
||||
keys := obj.sortedKeys()
|
||||
for i := range keys {
|
||||
if buf, err = keys[i].key.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, ": "...)
|
||||
if buf, err = keys[i].value.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if i < olen-1 {
|
||||
buf = append(buf, ", "...)
|
||||
}
|
||||
}
|
||||
|
||||
return append(buf, '}'), nil
|
||||
}
|
||||
|
||||
func (obj *lazyObj) AppendText(buf []byte) ([]byte, error) {
|
||||
return append(buf, obj.force().String()...), nil
|
||||
}
|
||||
|
||||
func (s *set) AppendText(buf []byte) ([]byte, error) {
|
||||
slen := s.Len()
|
||||
if slen == 0 {
|
||||
return append(buf, "set()"...), nil
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
buf = append(buf, '{')
|
||||
if buf, err = AppendDelimeted(buf, s.sortedKeys(), ", "); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return append(buf, '}'), nil
|
||||
}
|
||||
|
||||
func (c Call) AppendText(buf []byte) ([]byte, error) {
|
||||
if len(c) == 0 {
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
if buf, err = c[0].AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if buf, err = AppendDelimeted(append(buf, '('), c[1:], ", "); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(buf, ')'), nil
|
||||
}
|
||||
|
||||
func (ts *TemplateString) AppendText(buf []byte) ([]byte, error) {
|
||||
buf = append(buf, "$\""...)
|
||||
for _, p := range ts.Parts {
|
||||
switch x := p.(type) {
|
||||
case *Expr:
|
||||
buf = append(buf, '{')
|
||||
var err error
|
||||
if buf, err = x.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, '}')
|
||||
case *Term:
|
||||
if str, ok := x.Value.(String); ok {
|
||||
// TODO(anders): this is a bit of a mess, but as explained by the comment on
|
||||
// [EscapeTemplateStringStringPart], required as long as we rely on strconv for escaping, which adds
|
||||
// quotes around the string that we don't want here, and trying to "unappend" them is not nice at all..
|
||||
s := string(str)
|
||||
ulc := countUnescapedLeftCurly(s)
|
||||
sl := str.StringLength() + ulc - 2 // no surrounding quotes
|
||||
|
||||
if sl == len(s) { // no escaping needed
|
||||
buf = append(buf, s...)
|
||||
} else { // some escaping needed
|
||||
if sl == len(s)+ulc { // only unescaped {
|
||||
buf = AppendEscapedTemplateStringStringPart(buf, string(str))
|
||||
} else { // full escaping needed. this is expensive but luckily rare
|
||||
tmp := str.appendNoQuote(make([]byte, 0, sl))
|
||||
ets := EscapeTemplateStringStringPart(util.ByteSliceToString(tmp))
|
||||
buf = append(buf, ets...)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
if buf, err = x.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
default:
|
||||
buf = append(buf, "<invalid>"...)
|
||||
}
|
||||
}
|
||||
return append(buf, '"'), nil
|
||||
}
|
||||
|
||||
func (r Ref) AppendText(buf []byte) ([]byte, error) {
|
||||
reflen := len(r)
|
||||
if reflen == 0 {
|
||||
return buf, nil
|
||||
}
|
||||
if reflen == 1 {
|
||||
if s, ok := r[0].Value.(String); ok {
|
||||
// While a ref head is typically a Var, a lone String term should not be quoted
|
||||
return append(buf, s...), nil
|
||||
}
|
||||
return r[0].AppendText(buf)
|
||||
}
|
||||
if name, ok := BuiltinNameFromRef(r); ok {
|
||||
return append(buf, name...), nil
|
||||
}
|
||||
|
||||
var err error
|
||||
if s, ok := r[0].Value.(String); ok {
|
||||
buf = append(buf, s...)
|
||||
} else if buf, err = r[0].AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, p := range r[1:] {
|
||||
switch v := p.Value.(type) {
|
||||
case String:
|
||||
str := string(v)
|
||||
if IsVarCompatibleString(str) && !IsKeyword(str) {
|
||||
buf = append(append(buf, '.'), str...)
|
||||
} else {
|
||||
buf = append(buf, '[')
|
||||
// Determine whether we need the full JSON-escaped form
|
||||
if strings.ContainsFunc(str, isControlOrBackslash) {
|
||||
if buf, err = v.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
buf = append(append(append(buf, '"'), str...), '"')
|
||||
}
|
||||
buf = append(buf, ']')
|
||||
}
|
||||
default:
|
||||
buf = append(buf, '[')
|
||||
if buf, err = p.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, ']')
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (sc *SetComprehension) AppendText(buf []byte) ([]byte, error) {
|
||||
buf = append(buf, '{')
|
||||
var err error
|
||||
if buf, err = sc.Term.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf, err = sc.Body.AppendText(append(buf, " | "...)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(buf, '}'), nil
|
||||
}
|
||||
|
||||
func (ac *ArrayComprehension) AppendText(buf []byte) ([]byte, error) {
|
||||
buf = append(buf, '[')
|
||||
var err error
|
||||
if buf, err = ac.Term.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf, err = ac.Body.AppendText(append(buf, " | "...)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(buf, ']'), nil
|
||||
}
|
||||
|
||||
func (oc *ObjectComprehension) AppendText(buf []byte) ([]byte, error) {
|
||||
buf = append(buf, '{')
|
||||
var err error
|
||||
if buf, err = oc.Key.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, ": "...)
|
||||
if buf, err = oc.Value.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf, err = oc.Body.AppendText(append(buf, " | "...)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(buf, '}'), nil
|
||||
}
|
||||
+29
-16
@@ -19,7 +19,6 @@ type Transformer interface {
|
||||
// Transform iterates the AST and calls the Transform function on the
|
||||
// Transformer t for x before recursing.
|
||||
func Transform(t Transformer, x any) (any, error) {
|
||||
|
||||
if term, ok := x.(*Term); ok {
|
||||
return Transform(t, term.Value)
|
||||
}
|
||||
@@ -284,6 +283,19 @@ func Transform(t Transformer, x any) (any, error) {
|
||||
}
|
||||
}
|
||||
return y, nil
|
||||
case *TemplateString:
|
||||
for i := range y.Parts {
|
||||
if expr, ok := y.Parts[i].(*Expr); ok {
|
||||
transformed, err := Transform(t, expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if y.Parts[i], ok = transformed.(*Expr); !ok {
|
||||
return nil, fmt.Errorf("illegal transform: %T != %T", expr, transformed)
|
||||
}
|
||||
}
|
||||
}
|
||||
return y, nil
|
||||
default:
|
||||
return y, nil
|
||||
}
|
||||
@@ -291,29 +303,29 @@ func Transform(t Transformer, x any) (any, error) {
|
||||
|
||||
// TransformRefs calls the function f on all references under x.
|
||||
func TransformRefs(x any, f func(Ref) (Value, error)) (any, error) {
|
||||
t := &GenericTransformer{func(x any) (any, error) {
|
||||
t := NewGenericTransformer(func(x any) (any, error) {
|
||||
if r, ok := x.(Ref); ok {
|
||||
return f(r)
|
||||
}
|
||||
return x, nil
|
||||
}}
|
||||
})
|
||||
return Transform(t, x)
|
||||
}
|
||||
|
||||
// TransformVars calls the function f on all vars under x.
|
||||
func TransformVars(x any, f func(Var) (Value, error)) (any, error) {
|
||||
t := &GenericTransformer{func(x any) (any, error) {
|
||||
t := NewGenericTransformer(func(x any) (any, error) {
|
||||
if v, ok := x.(Var); ok {
|
||||
return f(v)
|
||||
}
|
||||
return x, nil
|
||||
}}
|
||||
})
|
||||
return Transform(t, x)
|
||||
}
|
||||
|
||||
// TransformComprehensions calls the functio nf on all comprehensions under x.
|
||||
// TransformComprehensions calls the function f on all comprehensions under x.
|
||||
func TransformComprehensions(x any, f func(any) (Value, error)) (any, error) {
|
||||
t := &GenericTransformer{func(x any) (any, error) {
|
||||
t := NewGenericTransformer(func(x any) (any, error) {
|
||||
switch x := x.(type) {
|
||||
case *ArrayComprehension:
|
||||
return f(x)
|
||||
@@ -323,7 +335,7 @@ func TransformComprehensions(x any, f func(any) (Value, error)) (any, error) {
|
||||
return f(x)
|
||||
}
|
||||
return x, nil
|
||||
}}
|
||||
})
|
||||
return Transform(t, x)
|
||||
}
|
||||
|
||||
@@ -387,11 +399,7 @@ func transformTerm(t Transformer, term *Term) (*Term, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := &Term{
|
||||
Value: v,
|
||||
Location: term.Location,
|
||||
}
|
||||
return r, nil
|
||||
return &Term{Value: v, Location: term.Location}, nil
|
||||
}
|
||||
|
||||
func transformValue(t Transformer, v Value) (Value, error) {
|
||||
@@ -407,13 +415,18 @@ func transformValue(t Transformer, v Value) (Value, error) {
|
||||
}
|
||||
|
||||
func transformVar(t Transformer, v Var) (Var, error) {
|
||||
v1, err := Transform(t, v)
|
||||
tv, err := t.Transform(v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
r, ok := v1.(Var)
|
||||
|
||||
if tv == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
r, ok := tv.(Var)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("illegal transform: %T != %T", v, v1)
|
||||
return "", fmt.Errorf("illegal transform: %T != %T", v, tv)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
+5
-6
@@ -11,12 +11,11 @@ func isRefSafe(ref Ref, safe VarSet) bool {
|
||||
case Call:
|
||||
return isCallSafe(head, safe)
|
||||
default:
|
||||
for v := range ref[0].Vars() {
|
||||
if !safe.Contains(v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
vis := varVisitorPool.Get().WithParams(SafetyCheckVisitorParams)
|
||||
vis.Walk(ref[0])
|
||||
isSafe := vis.Vars().DiffCount(safe) == 0
|
||||
varVisitorPool.Put(vis)
|
||||
return isSafe
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-2
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
// VarSet represents a set of variables.
|
||||
type VarSet map[Var]struct{}
|
||||
type VarSet map[Var]struct{ *Location }
|
||||
|
||||
// NewVarSet returns a new VarSet containing the specified variables.
|
||||
func NewVarSet(vs ...Var) VarSet {
|
||||
@@ -30,7 +30,16 @@ func NewVarSetOfSize(size int) VarSet {
|
||||
|
||||
// Add updates the set to include the variable "v".
|
||||
func (s VarSet) Add(v Var) {
|
||||
s[v] = struct{}{}
|
||||
if _, ok := s[v]; !ok {
|
||||
s[v] = struct{ *Location }{}
|
||||
}
|
||||
}
|
||||
|
||||
func (s VarSet) AddLocation(v Var, l *Location) {
|
||||
if entry, ok := s[v]; ok {
|
||||
entry.Location = l
|
||||
s[v] = entry
|
||||
}
|
||||
}
|
||||
|
||||
// Contains returns true if the set contains the variable "v".
|
||||
@@ -54,6 +63,7 @@ func (s VarSet) Diff(vs VarSet) VarSet {
|
||||
for v := range s {
|
||||
if !vs.Contains(v) {
|
||||
r.Add(v)
|
||||
r.AddLocation(v, s[v].Location)
|
||||
}
|
||||
}
|
||||
return r
|
||||
|
||||
+225
-624
File diff suppressed because it is too large
Load Diff
+329
-135
@@ -4,40 +4,109 @@
|
||||
|
||||
package ast
|
||||
|
||||
// Visitor defines the interface for iterating AST elements. The Visit function
|
||||
// can return a Visitor w which will be used to visit the children of the AST
|
||||
// element v. If the Visit function returns nil, the children will not be
|
||||
// visited.
|
||||
// Deprecated: use GenericVisitor or another visitor implementation
|
||||
type Visitor interface {
|
||||
Visit(v any) (w Visitor)
|
||||
}
|
||||
var (
|
||||
termTypeVisitor = newTypeVisitor[*Term]()
|
||||
varTypeVisitor = newTypeVisitor[Var]()
|
||||
exprTypeVisitor = newTypeVisitor[*Expr]()
|
||||
ruleTypeVisitor = newTypeVisitor[*Rule]()
|
||||
refTypeVisitor = newTypeVisitor[Ref]()
|
||||
bodyTypeVisitor = newTypeVisitor[Body]()
|
||||
withTypeVisitor = newTypeVisitor[*With]()
|
||||
)
|
||||
|
||||
// BeforeAndAfterVisitor wraps Visitor to provide hooks for being called before
|
||||
// and after the AST has been visited.
|
||||
// Deprecated: use GenericVisitor or another visitor implementation
|
||||
type BeforeAndAfterVisitor interface {
|
||||
Visitor
|
||||
Before(x any)
|
||||
After(x any)
|
||||
}
|
||||
type (
|
||||
// GenericVisitor provides a utility to walk over AST nodes using a
|
||||
// closure. If the closure returns true, the visitor will not walk
|
||||
// over AST nodes under x.
|
||||
GenericVisitor struct {
|
||||
f func(x any) bool
|
||||
}
|
||||
|
||||
// Walk iterates the AST by calling the Visit function on the Visitor
|
||||
// BeforeAfterVisitor provides a utility to walk over AST nodes using
|
||||
// closures. If the before closure returns true, the visitor will not
|
||||
// walk over AST nodes under x. The after closure is invoked always
|
||||
// after visiting a node.
|
||||
BeforeAfterVisitor struct {
|
||||
before func(x any) bool
|
||||
after func(x any)
|
||||
}
|
||||
|
||||
// VarVisitor walks AST nodes under a given node and collects all encountered
|
||||
// variables. The collected variables can be controlled by specifying
|
||||
// VarVisitorParams when creating the visitor.
|
||||
VarVisitor struct {
|
||||
params VarVisitorParams
|
||||
vars VarSet
|
||||
}
|
||||
|
||||
// VarVisitorParams contains settings for a VarVisitor.
|
||||
VarVisitorParams struct {
|
||||
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
|
||||
// can return a Visitor w which will be used to visit the children of the AST
|
||||
// element v. If the Visit function returns nil, the children will not be
|
||||
// visited.
|
||||
//
|
||||
// Deprecated: use [GenericVisitor] or another visitor implementation
|
||||
Visitor interface {
|
||||
Visit(v any) (w Visitor)
|
||||
}
|
||||
|
||||
// BeforeAndAfterVisitor wraps Visitor to provide hooks for being called before
|
||||
// and after the AST has been visited.
|
||||
//
|
||||
// Deprecated: use [GenericVisitor] or another visitor implementation
|
||||
BeforeAndAfterVisitor interface {
|
||||
Visitor
|
||||
Before(x any)
|
||||
After(x any)
|
||||
}
|
||||
|
||||
// typeVisitor is a generic visitor for a specific type T (the "generic" name was
|
||||
// however taken). Contrary to the [GenericVisitor], the typeVisitor only invokes
|
||||
// the visit function for nodes of type T, saving both CPU cycles and type assertions.
|
||||
// typeVisitor implementations carry no state, and can be shared freely across
|
||||
// goroutines. Access is private for the time being, as there is already inflation
|
||||
// in visitor types exposed in the AST package. The various WalkXXX functions however
|
||||
// now leverage typeVisitor under the hood.
|
||||
//
|
||||
// While a typeVisitor is generally a more performant option over a GenericVisitor,
|
||||
// it is not as flexible: a type visitor can only visit nodes of a single type T,
|
||||
// whereas a GenericVisitor visits all nodes. Adding to that, a typeVisitor can only
|
||||
// be instantiated for **concrete types** — not interfaces (e.g., [*Expr], not [Node]),
|
||||
// as reflection would be required to determine the concrete type at runtime, thus
|
||||
// nullifying the performance benefits of the typeVisitor in the first place.
|
||||
typeVisitor[T any] struct {
|
||||
typ any
|
||||
}
|
||||
)
|
||||
|
||||
// Walk iterates the AST by calling the Visit function on the [Visitor]
|
||||
// v for x before recursing.
|
||||
// Deprecated: use GenericVisitor.Walk
|
||||
//
|
||||
// Deprecated: use [GenericVisitor.Walk]
|
||||
func Walk(v Visitor, x any) {
|
||||
if bav, ok := v.(BeforeAndAfterVisitor); !ok {
|
||||
walk(v, x)
|
||||
} else {
|
||||
bav.Before(x)
|
||||
defer bav.After(x)
|
||||
walk(bav, x)
|
||||
bav.After(x)
|
||||
}
|
||||
}
|
||||
|
||||
// WalkBeforeAndAfter iterates the AST by calling the Visit function on the
|
||||
// Visitor v for x before recursing.
|
||||
// Deprecated: use GenericVisitor.Walk
|
||||
//
|
||||
// Deprecated: use [GenericVisitor.Walk]
|
||||
func WalkBeforeAndAfter(v BeforeAndAfterVisitor, x any) {
|
||||
Walk(v, x)
|
||||
}
|
||||
@@ -149,132 +218,258 @@ func walk(v Visitor, x any) {
|
||||
for i := range x.Symbols {
|
||||
Walk(w, x.Symbols[i])
|
||||
}
|
||||
case *TemplateString:
|
||||
for i := range x.Parts {
|
||||
Walk(w, x.Parts[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WalkVars calls the function f on all vars under x. If the function f
|
||||
// returns true, AST nodes under the last node will not be visited.
|
||||
func WalkVars(x any, f func(Var) bool) {
|
||||
vis := &GenericVisitor{func(x any) bool {
|
||||
if v, ok := x.(Var); ok {
|
||||
return f(v)
|
||||
}
|
||||
return false
|
||||
}}
|
||||
vis.Walk(x)
|
||||
varTypeVisitor.walk(x, f)
|
||||
}
|
||||
|
||||
// WalkClosures calls the function f on all closures under x. If the function f
|
||||
// returns true, AST nodes under the last node will not be visited.
|
||||
func WalkClosures(x any, f func(any) bool) {
|
||||
vis := &GenericVisitor{func(x any) bool {
|
||||
vis := NewGenericVisitor(func(x any) bool {
|
||||
switch x := x.(type) {
|
||||
case *ArrayComprehension, *ObjectComprehension, *SetComprehension, *Every:
|
||||
return f(x)
|
||||
}
|
||||
return false
|
||||
}}
|
||||
})
|
||||
vis.Walk(x)
|
||||
}
|
||||
|
||||
// WalkRefs calls the function f on all references under x. If the function f
|
||||
// returns true, AST nodes under the last node will not be visited.
|
||||
func WalkRefs(x any, f func(Ref) bool) {
|
||||
vis := &GenericVisitor{func(x any) bool {
|
||||
if r, ok := x.(Ref); ok {
|
||||
return f(r)
|
||||
}
|
||||
return false
|
||||
}}
|
||||
vis.Walk(x)
|
||||
refTypeVisitor.walk(x, f)
|
||||
}
|
||||
|
||||
// WalkTerms calls the function f on all terms under x. If the function f
|
||||
// returns true, AST nodes under the last node will not be visited.
|
||||
func WalkTerms(x any, f func(*Term) bool) {
|
||||
vis := &GenericVisitor{func(x any) bool {
|
||||
if term, ok := x.(*Term); ok {
|
||||
return f(term)
|
||||
}
|
||||
return false
|
||||
}}
|
||||
vis.Walk(x)
|
||||
termTypeVisitor.walk(x, f)
|
||||
}
|
||||
|
||||
// WalkWiths calls the function f on all with modifiers under x. If the function f
|
||||
// returns true, AST nodes under the last node will not be visited.
|
||||
func WalkWiths(x any, f func(*With) bool) {
|
||||
vis := &GenericVisitor{func(x any) bool {
|
||||
if w, ok := x.(*With); ok {
|
||||
return f(w)
|
||||
}
|
||||
return false
|
||||
}}
|
||||
vis.Walk(x)
|
||||
withTypeVisitor.walk(x, f)
|
||||
}
|
||||
|
||||
// WalkExprs calls the function f on all expressions under x. If the function f
|
||||
// returns true, AST nodes under the last node will not be visited.
|
||||
func WalkExprs(x any, f func(*Expr) bool) {
|
||||
vis := &GenericVisitor{func(x any) bool {
|
||||
if r, ok := x.(*Expr); ok {
|
||||
return f(r)
|
||||
}
|
||||
return false
|
||||
}}
|
||||
vis.Walk(x)
|
||||
exprTypeVisitor.walk(x, f)
|
||||
}
|
||||
|
||||
// WalkBodies calls the function f on all bodies under x. If the function f
|
||||
// returns true, AST nodes under the last node will not be visited.
|
||||
func WalkBodies(x any, f func(Body) bool) {
|
||||
vis := &GenericVisitor{func(x any) bool {
|
||||
if b, ok := x.(Body); ok {
|
||||
return f(b)
|
||||
}
|
||||
return false
|
||||
}}
|
||||
vis.Walk(x)
|
||||
bodyTypeVisitor.walk(x, f)
|
||||
}
|
||||
|
||||
// WalkRules calls the function f on all rules under x. If the function f
|
||||
// returns true, AST nodes under the last node will not be visited.
|
||||
func WalkRules(x any, f func(*Rule) bool) {
|
||||
vis := &GenericVisitor{func(x any) bool {
|
||||
if r, ok := x.(*Rule); ok {
|
||||
stop := f(r)
|
||||
// NOTE(tsandall): since rules cannot be embedded inside of queries
|
||||
// we can stop early if there is no else block.
|
||||
if stop || r.Else == nil {
|
||||
return true
|
||||
switch x := x.(type) {
|
||||
case *Module:
|
||||
for i := range x.Rules {
|
||||
if !f(x.Rules[i]) && x.Rules[i].Else != nil {
|
||||
WalkRules(x.Rules[i].Else, f)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}}
|
||||
vis.Walk(x)
|
||||
case *Rule:
|
||||
if !f(x) && x.Else != nil {
|
||||
WalkRules(x.Else, f)
|
||||
}
|
||||
default:
|
||||
ruleTypeVisitor.walk(x, f)
|
||||
}
|
||||
}
|
||||
|
||||
// WalkNodes calls the function f on all nodes under x. If the function f
|
||||
// returns true, AST nodes under the last node will not be visited.
|
||||
func WalkNodes(x any, f func(Node) bool) {
|
||||
vis := &GenericVisitor{func(x any) bool {
|
||||
vis := NewGenericVisitor(func(x any) bool {
|
||||
if n, ok := x.(Node); ok {
|
||||
return f(n)
|
||||
}
|
||||
return false
|
||||
}}
|
||||
})
|
||||
vis.Walk(x)
|
||||
}
|
||||
|
||||
// GenericVisitor provides a utility to walk over AST nodes using a
|
||||
// closure. If the closure returns true, the visitor will not walk
|
||||
// over AST nodes under x.
|
||||
type GenericVisitor struct {
|
||||
f func(x any) bool
|
||||
func newTypeVisitor[T any]() *typeVisitor[T] {
|
||||
var t T
|
||||
|
||||
return &typeVisitor[T]{typ: any(t)}
|
||||
}
|
||||
|
||||
func (tv *typeVisitor[T]) walkArgs(args Args, visit func(x T) bool) {
|
||||
// If T is not Args, avoid allocation by inlining the walk.
|
||||
if _, ok := tv.typ.(Args); !ok {
|
||||
for i := range args {
|
||||
tv.walk(args[i], visit)
|
||||
}
|
||||
} else {
|
||||
tv.walk(args, visit) // allocates
|
||||
}
|
||||
}
|
||||
|
||||
func (tv *typeVisitor[T]) walkBody(body Body, visit func(x T) bool) {
|
||||
if _, ok := tv.typ.(Body); !ok {
|
||||
for i := range body {
|
||||
tv.walk(body[i], visit)
|
||||
}
|
||||
} else {
|
||||
tv.walk(body, visit) // allocates
|
||||
}
|
||||
}
|
||||
|
||||
func (tv *typeVisitor[T]) walkRef(ref Ref, visit func(x T) bool) {
|
||||
if _, ok := tv.typ.(Ref); !ok {
|
||||
for i := range ref {
|
||||
tv.walk(ref[i], visit)
|
||||
}
|
||||
} else {
|
||||
tv.walk(ref, visit) // allocates
|
||||
}
|
||||
}
|
||||
|
||||
func (tv *typeVisitor[T]) walk(x any, visit func(x T) bool) {
|
||||
if v, ok := x.(T); ok && visit(v) {
|
||||
return
|
||||
}
|
||||
|
||||
switch x := x.(type) {
|
||||
case *Module:
|
||||
tv.walk(x.Package, visit)
|
||||
for i := range x.Imports {
|
||||
tv.walk(x.Imports[i], visit)
|
||||
}
|
||||
for i := range x.Rules {
|
||||
tv.walk(x.Rules[i], visit)
|
||||
}
|
||||
for i := range x.Annotations {
|
||||
tv.walk(x.Annotations[i], visit)
|
||||
}
|
||||
for i := range x.Comments {
|
||||
tv.walk(x.Comments[i], visit)
|
||||
}
|
||||
case *Package:
|
||||
tv.walkRef(x.Path, visit)
|
||||
case *Import:
|
||||
tv.walk(x.Path, visit)
|
||||
if _, ok := tv.typ.(Var); ok {
|
||||
tv.walk(x.Alias, visit)
|
||||
}
|
||||
case *Rule:
|
||||
tv.walk(x.Head, visit)
|
||||
tv.walkBody(x.Body, visit)
|
||||
if x.Else != nil {
|
||||
tv.walk(x.Else, visit)
|
||||
}
|
||||
case *Head:
|
||||
if _, ok := tv.typ.(Var); ok {
|
||||
tv.walk(x.Name, visit)
|
||||
}
|
||||
tv.walkArgs(x.Args, visit)
|
||||
if x.Key != nil {
|
||||
tv.walk(x.Key, visit)
|
||||
}
|
||||
if x.Value != nil {
|
||||
tv.walk(x.Value, visit)
|
||||
}
|
||||
case Body:
|
||||
for i := range x {
|
||||
tv.walk(x[i], visit)
|
||||
}
|
||||
case Args:
|
||||
for i := range x {
|
||||
tv.walk(x[i], visit)
|
||||
}
|
||||
case *Expr:
|
||||
switch ts := x.Terms.(type) {
|
||||
case *Term, *SomeDecl, *Every:
|
||||
tv.walk(ts, visit)
|
||||
case []*Term:
|
||||
for i := range ts {
|
||||
tv.walk(ts[i], visit)
|
||||
}
|
||||
}
|
||||
for i := range x.With {
|
||||
tv.walk(x.With[i], visit)
|
||||
}
|
||||
case *With:
|
||||
tv.walk(x.Target, visit)
|
||||
tv.walk(x.Value, visit)
|
||||
case *Term:
|
||||
tv.walk(x.Value, visit)
|
||||
case Ref:
|
||||
for i := range x {
|
||||
tv.walk(x[i], visit)
|
||||
}
|
||||
case *object:
|
||||
x.Foreach(func(k, v *Term) {
|
||||
tv.walk(k, visit)
|
||||
tv.walk(v, visit)
|
||||
})
|
||||
case Object:
|
||||
for _, k := range x.Keys() {
|
||||
tv.walk(k, visit)
|
||||
tv.walk(x.Get(k), visit)
|
||||
}
|
||||
case *Array:
|
||||
for i := range x.Len() {
|
||||
tv.walk(x.Elem(i), visit)
|
||||
}
|
||||
case Set:
|
||||
xSlice := x.Slice()
|
||||
for i := range xSlice {
|
||||
tv.walk(xSlice[i], visit)
|
||||
}
|
||||
case *ArrayComprehension:
|
||||
tv.walk(x.Term, visit)
|
||||
tv.walkBody(x.Body, visit)
|
||||
case *ObjectComprehension:
|
||||
tv.walk(x.Key, visit)
|
||||
tv.walk(x.Value, visit)
|
||||
tv.walkBody(x.Body, visit)
|
||||
case *SetComprehension:
|
||||
tv.walk(x.Term, visit)
|
||||
tv.walkBody(x.Body, visit)
|
||||
case Call:
|
||||
for i := range x {
|
||||
tv.walk(x[i], visit)
|
||||
}
|
||||
case *Every:
|
||||
if x.Key != nil {
|
||||
tv.walk(x.Key, visit)
|
||||
}
|
||||
tv.walk(x.Value, visit)
|
||||
tv.walk(x.Domain, visit)
|
||||
tv.walkBody(x.Body, visit)
|
||||
case *SomeDecl:
|
||||
for i := range x.Symbols {
|
||||
tv.walk(x.Symbols[i], visit)
|
||||
}
|
||||
case *TemplateString:
|
||||
for i := range x.Parts {
|
||||
tv.walk(x.Parts[i], visit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewGenericVisitor returns a new GenericVisitor that will invoke the function
|
||||
// f on AST nodes.
|
||||
// f on AST nodes. Note that while it returns a pointer, the creating a GenericVisitor
|
||||
// doesn't commonly allocate it on the heap, as long as it doesn't escape the function
|
||||
// in which it is created and used (as it's trivially inlined).
|
||||
func NewGenericVisitor(f func(x any) bool) *GenericVisitor {
|
||||
return &GenericVisitor{f}
|
||||
}
|
||||
@@ -306,7 +501,9 @@ func (vis *GenericVisitor) Walk(x any) {
|
||||
vis.Walk(x.Path)
|
||||
case *Import:
|
||||
vis.Walk(x.Path)
|
||||
vis.Walk(x.Alias)
|
||||
if x.Alias != "" {
|
||||
vis.f(x.Alias)
|
||||
}
|
||||
case *Rule:
|
||||
vis.Walk(x.Head)
|
||||
vis.Walk(x.Body)
|
||||
@@ -314,8 +511,12 @@ func (vis *GenericVisitor) Walk(x any) {
|
||||
vis.Walk(x.Else)
|
||||
}
|
||||
case *Head:
|
||||
vis.Walk(x.Name)
|
||||
vis.Walk(x.Args)
|
||||
if x.Name != "" {
|
||||
vis.f(x.Name)
|
||||
}
|
||||
if x.Args != nil {
|
||||
vis.Walk(x.Args)
|
||||
}
|
||||
if x.Key != nil {
|
||||
vis.Walk(x.Key)
|
||||
}
|
||||
@@ -395,18 +596,13 @@ func (vis *GenericVisitor) Walk(x any) {
|
||||
for i := range x.Symbols {
|
||||
vis.Walk(x.Symbols[i])
|
||||
}
|
||||
case *TemplateString:
|
||||
for i := range x.Parts {
|
||||
vis.Walk(x.Parts[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BeforeAfterVisitor provides a utility to walk over AST nodes using
|
||||
// closures. If the before closure returns true, the visitor will not
|
||||
// walk over AST nodes under x. The after closure is invoked always
|
||||
// after visiting a node.
|
||||
type BeforeAfterVisitor struct {
|
||||
before func(x any) bool
|
||||
after func(x any)
|
||||
}
|
||||
|
||||
// NewBeforeAfterVisitor returns a new BeforeAndAfterVisitor that
|
||||
// will invoke the functions before and after AST nodes.
|
||||
func NewBeforeAfterVisitor(before func(x any) bool, after func(x any)) *BeforeAfterVisitor {
|
||||
@@ -538,31 +734,29 @@ func (vis *BeforeAfterVisitor) Walk(x any) {
|
||||
}
|
||||
}
|
||||
|
||||
// VarVisitor walks AST nodes under a given node and collects all encountered
|
||||
// variables. The collected variables can be controlled by specifying
|
||||
// VarVisitorParams when creating the visitor.
|
||||
type VarVisitor struct {
|
||||
params VarVisitorParams
|
||||
vars VarSet
|
||||
}
|
||||
|
||||
// VarVisitorParams contains settings for a VarVisitor.
|
||||
type VarVisitorParams struct {
|
||||
SkipRefHead bool
|
||||
SkipRefCallHead bool
|
||||
SkipObjectKeys bool
|
||||
SkipClosures bool
|
||||
SkipWithTarget bool
|
||||
SkipSets bool
|
||||
}
|
||||
|
||||
// NewVarVisitor returns a new VarVisitor object.
|
||||
// NewVarVisitor returns a new [VarVisitor] object.
|
||||
func NewVarVisitor() *VarVisitor {
|
||||
return &VarVisitor{
|
||||
vars: NewVarSet(),
|
||||
}
|
||||
}
|
||||
|
||||
// ClearOrNewVarVisitor clears a non-nil [VarVisitor] or returns a new one.
|
||||
func ClearOrNewVarVisitor(vis *VarVisitor) *VarVisitor {
|
||||
if vis == nil {
|
||||
return NewVarVisitor()
|
||||
}
|
||||
|
||||
return vis.Clear()
|
||||
}
|
||||
|
||||
// ClearOrNew resets the visitor to its initial state, or returns a new one if nil.
|
||||
//
|
||||
// Deprecated: use [ClearOrNewVarVisitor] instead.
|
||||
func (vis *VarVisitor) ClearOrNew() *VarVisitor {
|
||||
return ClearOrNewVarVisitor(vis)
|
||||
}
|
||||
|
||||
// Clear resets the visitor to its initial state, and returns it for chaining.
|
||||
func (vis *VarVisitor) Clear() *VarVisitor {
|
||||
vis.params = VarVisitorParams{}
|
||||
@@ -571,14 +765,6 @@ func (vis *VarVisitor) Clear() *VarVisitor {
|
||||
return vis
|
||||
}
|
||||
|
||||
// ClearOrNew returns a new VarVisitor if vis is nil, or else a cleared VarVisitor.
|
||||
func (vis *VarVisitor) ClearOrNew() *VarVisitor {
|
||||
if vis == nil {
|
||||
return NewVarVisitor()
|
||||
}
|
||||
return vis.Clear()
|
||||
}
|
||||
|
||||
// WithParams sets the parameters in params on vis.
|
||||
func (vis *VarVisitor) WithParams(params VarVisitorParams) *VarVisitor {
|
||||
vis.params = params
|
||||
@@ -594,7 +780,7 @@ func (vis *VarVisitor) Add(v Var) {
|
||||
}
|
||||
}
|
||||
|
||||
// Vars returns a VarSet that contains collected vars.
|
||||
// Vars returns a [VarSet] that contains collected vars.
|
||||
func (vis *VarVisitor) Vars() VarSet {
|
||||
return vis.vars
|
||||
}
|
||||
@@ -621,7 +807,7 @@ func (vis *VarVisitor) visit(v any) bool {
|
||||
}
|
||||
if vis.params.SkipClosures {
|
||||
switch v := v.(type) {
|
||||
case *ArrayComprehension, *ObjectComprehension, *SetComprehension:
|
||||
case *ArrayComprehension, *ObjectComprehension, *SetComprehension, *TemplateString:
|
||||
return true
|
||||
case *Expr:
|
||||
if ev, ok := v.Terms.(*Every); ok {
|
||||
@@ -685,15 +871,20 @@ 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
|
||||
}
|
||||
|
||||
// Walk iterates the AST by calling the function f on the
|
||||
// GenericVisitor before recursing. Contrary to the generic Walk, this
|
||||
// does not require allocating the visitor from heap.
|
||||
// Walk iterates the AST by calling the function f on the [VarVisitor] before recursing.
|
||||
// Contrary to the deprecated [Walk] function, this does not require allocating the visitor from heap.
|
||||
func (vis *VarVisitor) Walk(x any) {
|
||||
if vis.visit(x) {
|
||||
return
|
||||
@@ -701,16 +892,9 @@ func (vis *VarVisitor) Walk(x any) {
|
||||
|
||||
switch x := x.(type) {
|
||||
case *Module:
|
||||
vis.Walk(x.Package)
|
||||
for i := range x.Imports {
|
||||
vis.Walk(x.Imports[i])
|
||||
}
|
||||
for i := range x.Rules {
|
||||
vis.Walk(x.Rules[i])
|
||||
}
|
||||
for i := range x.Comments {
|
||||
vis.Walk(x.Comments[i])
|
||||
}
|
||||
case *Package:
|
||||
vis.WalkRef(x.Path)
|
||||
case *Import:
|
||||
@@ -758,14 +942,17 @@ 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)
|
||||
}
|
||||
case *object:
|
||||
x.Foreach(func(k, _ *Term) {
|
||||
x.Foreach(func(k, v *Term) {
|
||||
vis.Walk(k)
|
||||
vis.Walk(x.Get(k))
|
||||
vis.Walk(v)
|
||||
})
|
||||
case *Array:
|
||||
x.Foreach(func(t *Term) {
|
||||
@@ -801,6 +988,10 @@ func (vis *VarVisitor) Walk(x any) {
|
||||
for i := range x.Symbols {
|
||||
vis.Walk(x.Symbols[i])
|
||||
}
|
||||
case *TemplateString:
|
||||
for i := range x.Parts {
|
||||
vis.Walk(x.Parts[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -820,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+127
-189
@@ -6,9 +6,7 @@
|
||||
package bundle
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -24,6 +22,8 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/open-policy-agent/opa/internal/file/archive"
|
||||
"github.com/open-policy-agent/opa/internal/merge"
|
||||
"github.com/open-policy-agent/opa/v1/ast"
|
||||
@@ -51,6 +51,10 @@ const (
|
||||
SnapshotBundleType = "snapshot"
|
||||
)
|
||||
|
||||
var (
|
||||
empty Bundle
|
||||
)
|
||||
|
||||
// Bundle represents a loaded bundle. The bundle can contain data and policies.
|
||||
type Bundle struct {
|
||||
Signatures SignaturesConfig
|
||||
@@ -96,7 +100,7 @@ type SignaturesConfig struct {
|
||||
|
||||
// isEmpty returns if the SignaturesConfig is empty.
|
||||
func (s SignaturesConfig) isEmpty() bool {
|
||||
return reflect.DeepEqual(s, SignaturesConfig{})
|
||||
return s.Signatures == nil && s.Plugin == ""
|
||||
}
|
||||
|
||||
// DecodedSignature represents the decoded JWT payload.
|
||||
@@ -186,7 +190,6 @@ func (m *Manifest) SetRegoVersion(v ast.RegoVersion) {
|
||||
|
||||
// Equal returns true if m is semantically equivalent to other.
|
||||
func (m Manifest) Equal(other Manifest) bool {
|
||||
|
||||
// This is safe since both are passed by value.
|
||||
m.Init()
|
||||
other.Init()
|
||||
@@ -323,7 +326,6 @@ func (ss stringSet) Equal(other stringSet) bool {
|
||||
}
|
||||
|
||||
func (m *Manifest) validateAndInjectDefaults(b Bundle) error {
|
||||
|
||||
m.Init()
|
||||
|
||||
// Validate roots in bundle.
|
||||
@@ -337,7 +339,7 @@ func (m *Manifest) validateAndInjectDefaults(b Bundle) error {
|
||||
for i := range len(roots) - 1 {
|
||||
for j := i + 1; j < len(roots); j++ {
|
||||
if RootPathsOverlap(roots[i], roots[j]) {
|
||||
return fmt.Errorf("manifest has overlapped roots: '%v' and '%v'", roots[i], roots[j])
|
||||
return fmt.Errorf("manifest has overlapped roots: '%s' and '%s'", roots[i], roots[j])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -349,7 +351,7 @@ func (m *Manifest) validateAndInjectDefaults(b Bundle) error {
|
||||
found = RootPathsContain(roots, path)
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("manifest roots %v do not permit '%v' in module '%v'", roots, module.Parsed.Package, module.Path)
|
||||
return fmt.Errorf("manifest roots %v do not permit '%v' in module '%s'", roots, module.Parsed.Package, module.Path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,7 +370,7 @@ func (m *Manifest) validateAndInjectDefaults(b Bundle) error {
|
||||
|
||||
// Ensure wasm module entrypoint in within bundle roots
|
||||
if !RootPathsContain(roots, wmConfig.Entrypoint) {
|
||||
return fmt.Errorf("manifest roots %v do not permit '%v' entrypoint for wasm module '%v'", roots, wmConfig.Entrypoint, wmConfig.Module)
|
||||
return fmt.Errorf("manifest roots %v do not permit '%s' entrypoint for wasm module '%s'", roots, wmConfig.Entrypoint, wmConfig.Module)
|
||||
}
|
||||
|
||||
if _, ok := seenEps[wmConfig.Entrypoint]; ok {
|
||||
@@ -504,14 +506,13 @@ func NewReader(r io.Reader) *Reader {
|
||||
// NewCustomReader returns a new Reader configured to use the
|
||||
// specified DirectoryLoader.
|
||||
func NewCustomReader(loader DirectoryLoader) *Reader {
|
||||
nr := Reader{
|
||||
return &Reader{
|
||||
loader: loader,
|
||||
metrics: metrics.New(),
|
||||
metrics: metrics.NoOp(),
|
||||
files: make(map[string]FileInfo),
|
||||
sizeLimitBytes: DefaultSizeLimitBytes + 1,
|
||||
lazyLoadingMode: HasExtension(),
|
||||
}
|
||||
return &nr
|
||||
}
|
||||
|
||||
// IncludeManifestInData sets whether the manifest metadata should be
|
||||
@@ -620,24 +621,17 @@ func (r *Reader) ParserOptions() ast.ParserOptions {
|
||||
|
||||
// Read returns a new Bundle loaded from the reader.
|
||||
func (r *Reader) Read() (Bundle, error) {
|
||||
|
||||
var bundle Bundle
|
||||
var descriptors []*Descriptor
|
||||
var err error
|
||||
var raw []Raw
|
||||
|
||||
bundle.Signatures, bundle.Patch, descriptors, err = preProcessBundle(r.loader, r.skipVerify, r.sizeLimitBytes)
|
||||
bundle, descriptors, err := preProcessBundle(r.loader, r.skipVerify, r.sizeLimitBytes)
|
||||
if err != nil {
|
||||
return bundle, err
|
||||
return empty, err
|
||||
}
|
||||
|
||||
bundle.lazyLoadingMode = r.lazyLoadingMode
|
||||
bundle.sizeLimitBytes = r.sizeLimitBytes
|
||||
|
||||
if bundle.Type() == SnapshotBundleType {
|
||||
err = r.checkSignaturesAndDescriptors(bundle.Signatures)
|
||||
if err != nil {
|
||||
return bundle, err
|
||||
if err := r.checkSignaturesAndDescriptors(bundle.Signatures); err != nil {
|
||||
return empty, err
|
||||
}
|
||||
|
||||
bundle.Data = map[string]any{}
|
||||
@@ -647,7 +641,7 @@ func (r *Reader) Read() (Bundle, error) {
|
||||
for _, f := range descriptors {
|
||||
buf, err := readFile(f, r.sizeLimitBytes)
|
||||
if err != nil {
|
||||
return bundle, err
|
||||
return empty, err
|
||||
}
|
||||
|
||||
// verify the file content
|
||||
@@ -663,7 +657,7 @@ func (r *Reader) Read() (Bundle, error) {
|
||||
delete(r.files, path)
|
||||
} else {
|
||||
if err = r.verifyBundleFile(path, buf); err != nil {
|
||||
return bundle, err
|
||||
return empty, err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -690,7 +684,7 @@ func (r *Reader) Read() (Bundle, error) {
|
||||
p = modulePathWithPrefix(r.name, fullPath)
|
||||
}
|
||||
|
||||
raw = append(raw, Raw{Path: p, Value: bs, module: &mf})
|
||||
bundle.Raw = append(bundle.Raw, Raw{Path: p, Value: bs, module: &mf})
|
||||
}
|
||||
} else if filepath.Base(path) == WasmFile {
|
||||
bundle.WasmModules = append(bundle.WasmModules, WasmModuleFile{
|
||||
@@ -706,7 +700,7 @@ func (r *Reader) Read() (Bundle, error) {
|
||||
})
|
||||
} else if filepath.Base(path) == dataFile {
|
||||
if r.lazyLoadingMode {
|
||||
raw = append(raw, Raw{Path: path, Value: buf.Bytes()})
|
||||
bundle.Raw = append(bundle.Raw, Raw{Path: path, Value: buf.Bytes()})
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -717,16 +711,16 @@ func (r *Reader) Read() (Bundle, error) {
|
||||
r.metrics.Timer(metrics.RegoDataParse).Stop()
|
||||
|
||||
if err != nil {
|
||||
return bundle, fmt.Errorf("bundle load failed on %v: %w", r.fullPath(path), err)
|
||||
return empty, fmt.Errorf("bundle load failed on %v: %w", r.fullPath(path), err)
|
||||
}
|
||||
|
||||
if err := insertValue(&bundle, path, value); err != nil {
|
||||
return bundle, err
|
||||
if err := insertValue(bundle, path, value); err != nil {
|
||||
return empty, err
|
||||
}
|
||||
|
||||
} else if filepath.Base(path) == yamlDataFile || filepath.Base(path) == ymlDataFile {
|
||||
if r.lazyLoadingMode {
|
||||
raw = append(raw, Raw{Path: path, Value: buf.Bytes()})
|
||||
bundle.Raw = append(bundle.Raw, Raw{Path: path, Value: buf.Bytes()})
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -737,16 +731,16 @@ func (r *Reader) Read() (Bundle, error) {
|
||||
r.metrics.Timer(metrics.RegoDataParse).Stop()
|
||||
|
||||
if err != nil {
|
||||
return bundle, fmt.Errorf("bundle load failed on %v: %w", r.fullPath(path), err)
|
||||
return empty, fmt.Errorf("bundle load failed on %v: %w", r.fullPath(path), err)
|
||||
}
|
||||
|
||||
if err := insertValue(&bundle, path, value); err != nil {
|
||||
return bundle, err
|
||||
if err := insertValue(bundle, path, value); err != nil {
|
||||
return empty, err
|
||||
}
|
||||
|
||||
} else if strings.HasSuffix(path, ManifestExt) {
|
||||
if err := util.NewJSONDecoder(&buf).Decode(&bundle.Manifest); err != nil {
|
||||
return bundle, fmt.Errorf("bundle load failed on manifest decode: %w", err)
|
||||
return empty, fmt.Errorf("bundle load failed on manifest decode: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -754,52 +748,63 @@ func (r *Reader) Read() (Bundle, error) {
|
||||
// Parse modules
|
||||
popts := r.ParserOptions()
|
||||
popts.RegoVersion = bundle.RegoVersion(popts.EffectiveRegoVersion())
|
||||
for _, mf := range modules {
|
||||
modulePopts := popts
|
||||
|
||||
g := &errgroup.Group{}
|
||||
r.metrics.Timer(metrics.RegoModuleParse).Start()
|
||||
|
||||
for i, mf := range modules {
|
||||
mpopts := popts
|
||||
if regoVersion, err := bundle.RegoVersionForFile(mf.RelativePath, popts.EffectiveRegoVersion()); err != nil {
|
||||
return bundle, err
|
||||
return *bundle, err
|
||||
} else if regoVersion != ast.RegoUndefined {
|
||||
// We don't expect ast.RegoUndefined here, but don't override configured rego-version if we do just to be extra protective
|
||||
modulePopts.RegoVersion = regoVersion
|
||||
// We don't expect ast.RegoUndefined here, but don't override
|
||||
// configured rego-version if we do just to be extra protective
|
||||
mpopts.RegoVersion = regoVersion
|
||||
}
|
||||
r.metrics.Timer(metrics.RegoModuleParse).Start()
|
||||
mf.Parsed, err = ast.ParseModuleWithOpts(mf.Path, util.ByteSliceToString(mf.Raw), modulePopts)
|
||||
r.metrics.Timer(metrics.RegoModuleParse).Stop()
|
||||
if err != nil {
|
||||
return bundle, err
|
||||
}
|
||||
bundle.Modules = append(bundle.Modules, mf)
|
||||
|
||||
g.Go(func() (err error) {
|
||||
if mf.Parsed, err = ast.ParseModuleWithOpts(mf.Path, util.ByteSliceToString(mf.Raw), mpopts); err == nil {
|
||||
modules[i] = mf
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
err = g.Wait()
|
||||
r.metrics.Timer(metrics.RegoModuleParse).Stop()
|
||||
if err != nil {
|
||||
return empty, err
|
||||
}
|
||||
|
||||
bundle.Modules = modules
|
||||
|
||||
if bundle.Type() == DeltaBundleType {
|
||||
if len(bundle.Data) != 0 {
|
||||
return bundle, errors.New("delta bundle expected to contain only patch file but data files found")
|
||||
return empty, errors.New("delta bundle expected to contain only patch file but data files found")
|
||||
}
|
||||
|
||||
if len(bundle.Modules) != 0 {
|
||||
return bundle, errors.New("delta bundle expected to contain only patch file but policy files found")
|
||||
return empty, errors.New("delta bundle expected to contain only patch file but policy files found")
|
||||
}
|
||||
|
||||
if len(bundle.WasmModules) != 0 {
|
||||
return bundle, errors.New("delta bundle expected to contain only patch file but wasm files found")
|
||||
return empty, errors.New("delta bundle expected to contain only patch file but wasm files found")
|
||||
}
|
||||
|
||||
if r.persist {
|
||||
return bundle, errors.New("'persist' property is true in config. persisting delta bundle to disk is not supported")
|
||||
return empty, errors.New(
|
||||
"'persist' property is true in config. persisting delta bundle to disk is not supported")
|
||||
}
|
||||
}
|
||||
|
||||
// check if the bundle signatures specify any files that weren't found in the bundle
|
||||
if bundle.Type() == SnapshotBundleType && len(r.files) != 0 {
|
||||
extra := []string{}
|
||||
for k := range r.files {
|
||||
extra = append(extra, k)
|
||||
}
|
||||
return bundle, fmt.Errorf("file(s) %v specified in bundle signatures but not found in the target bundle", extra)
|
||||
return empty, fmt.Errorf(
|
||||
"file(s) %v specified in bundle signatures but not found in the target bundle", util.Keys(r.files))
|
||||
}
|
||||
|
||||
if err := bundle.Manifest.validateAndInjectDefaults(bundle); err != nil {
|
||||
return bundle, err
|
||||
if err := bundle.Manifest.validateAndInjectDefaults(*bundle); err != nil {
|
||||
return empty, err
|
||||
}
|
||||
|
||||
// Inject the wasm module entrypoint refs into the WasmModuleFile structs
|
||||
@@ -812,36 +817,33 @@ func (r *Reader) Read() (Bundle, error) {
|
||||
for _, entrypoint := range entrypoints {
|
||||
ref, err := ast.PtrRef(ast.DefaultRootDocument, entrypoint)
|
||||
if err != nil {
|
||||
return bundle, fmt.Errorf("failed to parse wasm module entrypoint '%s': %s", entrypoint, err)
|
||||
return empty, fmt.Errorf("failed to parse wasm module entrypoint '%s': %s", entrypoint, err)
|
||||
}
|
||||
bundle.WasmModules[i].Entrypoints = append(bundle.WasmModules[i].Entrypoints, ref)
|
||||
}
|
||||
}
|
||||
|
||||
if r.includeManifestInData {
|
||||
var metadata map[string]any
|
||||
|
||||
b, err := json.Marshal(&bundle.Manifest)
|
||||
if err != nil {
|
||||
return bundle, fmt.Errorf("bundle load failed on manifest marshal: %w", err)
|
||||
return empty, fmt.Errorf("bundle load failed on manifest marshal: %w", err)
|
||||
}
|
||||
|
||||
err = util.UnmarshalJSON(b, &metadata)
|
||||
if err != nil {
|
||||
return bundle, fmt.Errorf("bundle load failed on manifest unmarshal: %w", err)
|
||||
var metadata map[string]any
|
||||
if err := util.UnmarshalJSON(b, &metadata); err != nil {
|
||||
return empty, fmt.Errorf("bundle load failed on manifest unmarshal: %w", err)
|
||||
}
|
||||
|
||||
// For backwards compatibility always write to the old unnamed manifest path
|
||||
// This will *not* be correct if >1 bundle is in use...
|
||||
if err := bundle.insertData(legacyManifestStoragePath, metadata); err != nil {
|
||||
return bundle, fmt.Errorf("bundle load failed on %v: %w", legacyRevisionStoragePath, err)
|
||||
return empty, fmt.Errorf("bundle load failed on %v: %w", legacyRevisionStoragePath, err)
|
||||
}
|
||||
}
|
||||
|
||||
bundle.Etag = r.etag
|
||||
bundle.Raw = raw
|
||||
|
||||
return bundle, nil
|
||||
return *bundle, nil
|
||||
}
|
||||
|
||||
func (r *Reader) isFileExcluded(path string) bool {
|
||||
@@ -869,10 +871,9 @@ func (r *Reader) checkSignaturesAndDescriptors(signatures SignaturesConfig) erro
|
||||
}
|
||||
|
||||
// verify the JWT signatures included in the `.signatures.json` file
|
||||
if err := r.verifyBundleSignature(signatures); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.verifyBundleSignature(signatures)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -931,19 +932,10 @@ func (w *Writer) DisableFormat(yes bool) *Writer {
|
||||
|
||||
// Write writes the bundle to the writer's output stream.
|
||||
func (w *Writer) Write(bundle Bundle) error {
|
||||
gw := gzip.NewWriter(w.w)
|
||||
tw := tar.NewWriter(gw)
|
||||
tw := archive.NewTarGzWriter(w.w)
|
||||
|
||||
bundleType := bundle.Type()
|
||||
|
||||
if bundleType == SnapshotBundleType {
|
||||
var buf bytes.Buffer
|
||||
|
||||
if err := json.NewEncoder(&buf).Encode(bundle.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := archive.WriteFile(tw, "data.json", buf.Bytes()); err != nil {
|
||||
if bundle.Type() == SnapshotBundleType {
|
||||
if err := tw.WriteJSONFile("/data.json", bundle.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -953,7 +945,7 @@ func (w *Writer) Write(bundle Bundle) error {
|
||||
path = module.Path
|
||||
}
|
||||
|
||||
if err := archive.WriteFile(tw, path, module.Raw); err != nil {
|
||||
if err := tw.WriteFile(util.WithPrefix(path, "/"), module.Raw); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -969,55 +961,48 @@ func (w *Writer) Write(bundle Bundle) error {
|
||||
if err := w.writePlan(tw, bundle); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if bundleType == DeltaBundleType {
|
||||
if err := writePatch(tw, bundle); err != nil {
|
||||
} else if bundle.Type() == DeltaBundleType {
|
||||
if err := tw.WriteJSONFile("/patch.json", bundle.Patch); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := writeManifest(tw, bundle); err != nil {
|
||||
return err
|
||||
if !bundle.Manifest.Empty() {
|
||||
if err := tw.WriteJSONFile("/.manifest", bundle.Manifest); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := tw.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gw.Close()
|
||||
return tw.Close()
|
||||
}
|
||||
|
||||
func (w *Writer) writeWasm(tw *tar.Writer, bundle Bundle) error {
|
||||
func (w *Writer) writeWasm(tw *archive.TarGzWriter, bundle Bundle) error {
|
||||
for _, wm := range bundle.WasmModules {
|
||||
path := wm.URL
|
||||
if w.usePath {
|
||||
path = wm.Path
|
||||
}
|
||||
|
||||
err := archive.WriteFile(tw, path, wm.Raw)
|
||||
if err != nil {
|
||||
if err := tw.WriteFile(util.WithPrefix(path, "/"), wm.Raw); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(bundle.Wasm) > 0 {
|
||||
err := archive.WriteFile(tw, "/"+WasmFile, bundle.Wasm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(bundle.Wasm) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
return tw.WriteFile(util.WithPrefix(WasmFile, "/"), bundle.Wasm)
|
||||
}
|
||||
|
||||
func (w *Writer) writePlan(tw *tar.Writer, bundle Bundle) error {
|
||||
func (w *Writer) writePlan(tw *archive.TarGzWriter, bundle Bundle) error {
|
||||
for _, wm := range bundle.PlanModules {
|
||||
path := wm.URL
|
||||
if w.usePath {
|
||||
path = wm.Path
|
||||
}
|
||||
|
||||
err := archive.WriteFile(tw, path, wm.Raw)
|
||||
if err != nil {
|
||||
if err := tw.WriteFile(util.WithPrefix(path, "/"), wm.Raw); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -1025,34 +1010,7 @@ func (w *Writer) writePlan(tw *tar.Writer, bundle Bundle) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeManifest(tw *tar.Writer, bundle Bundle) error {
|
||||
|
||||
if bundle.Manifest.Empty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
if err := json.NewEncoder(&buf).Encode(bundle.Manifest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return archive.WriteFile(tw, ManifestExt, buf.Bytes())
|
||||
}
|
||||
|
||||
func writePatch(tw *tar.Writer, bundle Bundle) error {
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
if err := json.NewEncoder(&buf).Encode(bundle.Patch); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return archive.WriteFile(tw, patchFile, buf.Bytes())
|
||||
}
|
||||
|
||||
func writeSignatures(tw *tar.Writer, bundle Bundle) error {
|
||||
|
||||
func writeSignatures(tw *archive.TarGzWriter, bundle Bundle) error {
|
||||
if bundle.Signatures.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
@@ -1062,7 +1020,7 @@ func writeSignatures(tw *tar.Writer, bundle Bundle) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return archive.WriteFile(tw, fmt.Sprintf(".%v", SignaturesFile), bs)
|
||||
return tw.WriteFile(util.WithPrefix(SignaturesFile, "/."), bs)
|
||||
}
|
||||
|
||||
func hashBundleFiles(hash SignatureHasher, b *Bundle) ([]FileInfo, error) {
|
||||
@@ -1115,8 +1073,7 @@ func hashBundleFiles(hash SignatureHasher, b *Bundle) ([]FileInfo, error) {
|
||||
return files, err
|
||||
}
|
||||
|
||||
bs, err = hash.HashFile(result)
|
||||
if err != nil {
|
||||
if bs, err = hash.HashFile(result); err != nil {
|
||||
return files, err
|
||||
}
|
||||
|
||||
@@ -1227,10 +1184,6 @@ func (b *Bundle) GenerateSignature(signingConfig *SigningConfig, keyID string, u
|
||||
return err
|
||||
}
|
||||
|
||||
if b.Signatures.isEmpty() {
|
||||
b.Signatures = SignaturesConfig{}
|
||||
}
|
||||
|
||||
if signingConfig.Plugin != "" {
|
||||
b.Signatures.Plugin = signingConfig.Plugin
|
||||
}
|
||||
@@ -1243,7 +1196,6 @@ func (b *Bundle) GenerateSignature(signingConfig *SigningConfig, keyID string, u
|
||||
// ParsedModules returns a map of parsed modules with names that are
|
||||
// unique and human readable for the given a bundle name.
|
||||
func (b *Bundle) ParsedModules(bundleName string) map[string]*ast.Module {
|
||||
|
||||
mods := make(map[string]*ast.Module, len(b.Modules))
|
||||
|
||||
for _, mf := range b.Modules {
|
||||
@@ -1255,9 +1207,10 @@ func (b *Bundle) ParsedModules(bundleName string) map[string]*ast.Module {
|
||||
|
||||
func (b *Bundle) RegoVersion(def ast.RegoVersion) ast.RegoVersion {
|
||||
if v := b.Manifest.RegoVersion; v != nil {
|
||||
if *v == 0 {
|
||||
switch *v {
|
||||
case 0:
|
||||
return ast.RegoV0
|
||||
} else if *v == 1 {
|
||||
case 1:
|
||||
return ast.RegoV1
|
||||
}
|
||||
}
|
||||
@@ -1328,10 +1281,6 @@ func (m *Manifest) numericRegoVersionForFile(path string) (*int, error) {
|
||||
// Equal returns true if this bundle's contents equal the other bundle's
|
||||
// contents.
|
||||
func (b Bundle) Equal(other Bundle) bool {
|
||||
if !reflect.DeepEqual(b.Data, other.Data) {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(b.Modules) != len(other.Modules) {
|
||||
return false
|
||||
}
|
||||
@@ -1357,6 +1306,10 @@ func (b Bundle) Equal(other Bundle) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(b.Data, other.Data) {
|
||||
return false
|
||||
}
|
||||
|
||||
return bytes.Equal(b.Wasm, other.Wasm)
|
||||
}
|
||||
|
||||
@@ -1487,7 +1440,6 @@ func Merge(bundles []*Bundle) (*Bundle, error) {
|
||||
// If usePath is true, per-file rego-versions will be calculated using the file's ModuleFile.Path; otherwise, the file's
|
||||
// ModuleFile.URL will be used.
|
||||
func MergeWithRegoVersion(bundles []*Bundle, regoVersion ast.RegoVersion, usePath bool) (*Bundle, error) {
|
||||
|
||||
if len(bundles) == 0 {
|
||||
return nil, errors.New("expected at least one bundle")
|
||||
}
|
||||
@@ -1512,7 +1464,6 @@ func MergeWithRegoVersion(bundles []*Bundle, regoVersion ast.RegoVersion, usePat
|
||||
var result Bundle
|
||||
|
||||
for _, b := range bundles {
|
||||
|
||||
if b.Manifest.Roots == nil {
|
||||
return nil, errors.New("bundle manifest not initialized")
|
||||
}
|
||||
@@ -1607,16 +1558,11 @@ func bundleRelativePath(m ModuleFile, usePath bool) string {
|
||||
}
|
||||
|
||||
func bundleAbsolutePath(m ModuleFile, usePath bool) string {
|
||||
var p string
|
||||
p := m.URL
|
||||
if usePath {
|
||||
p = m.Path
|
||||
} else {
|
||||
p = m.URL
|
||||
}
|
||||
if !path.IsAbs(p) {
|
||||
p = "/" + p
|
||||
}
|
||||
return path.Clean(p)
|
||||
return path.Clean(util.WithPrefix(p, "/"))
|
||||
}
|
||||
|
||||
// RootPathsOverlap takes in two bundle root paths and returns true if they overlap.
|
||||
@@ -1642,7 +1588,6 @@ func rootPathSegments(path string) []string {
|
||||
}
|
||||
|
||||
func rootContains(root []string, other []string) bool {
|
||||
|
||||
// A single segment, empty string root always contains the other.
|
||||
if len(root) == 1 && root[0] == "" {
|
||||
return true
|
||||
@@ -1674,7 +1619,7 @@ func getNormalizedPath(path string) []string {
|
||||
// other hand, if the path is empty, filepath.Dir will return '.'.
|
||||
// Note: filepath.Dir can return paths with '\' separators, always use
|
||||
// filepath.ToSlash to keep them normalized.
|
||||
dirpath := strings.TrimLeft(normalizePath(filepath.Dir(path)), "/.")
|
||||
dirpath := strings.TrimLeft(filepath.ToSlash(filepath.Dir(path)), "/.")
|
||||
var key []string
|
||||
if dirpath != "" {
|
||||
key = strings.Split(dirpath, "/")
|
||||
@@ -1701,56 +1646,52 @@ func dfs(value any, path string, fn func(string, any) (bool, error)) error {
|
||||
}
|
||||
|
||||
func modulePathWithPrefix(bundleName string, modulePath string) string {
|
||||
// Default prefix is just the bundle name
|
||||
prefix := bundleName
|
||||
|
||||
// Bundle names are sometimes just file paths, some of which
|
||||
// are full urls (file:///foo/). Parse these and only use the path.
|
||||
parsed, err := url.Parse(bundleName)
|
||||
if err == nil {
|
||||
prefix = filepath.Join(parsed.Host, parsed.Path)
|
||||
return path.Join(parsed.Host, parsed.Path, modulePath)
|
||||
}
|
||||
|
||||
// Note: filepath.Join can return paths with '\' separators, always use
|
||||
// filepath.ToSlash to keep them normalized.
|
||||
return normalizePath(filepath.Join(prefix, modulePath))
|
||||
return path.Join(bundleName, modulePath)
|
||||
}
|
||||
|
||||
// IsStructuredDoc checks if the file name equals a structured file extension ex. ".json"
|
||||
func IsStructuredDoc(name string) bool {
|
||||
return filepath.Base(name) == dataFile || filepath.Base(name) == yamlDataFile ||
|
||||
filepath.Base(name) == SignaturesFile || filepath.Base(name) == ManifestExt
|
||||
base := filepath.Base(name)
|
||||
return base == dataFile || base == yamlDataFile || base == SignaturesFile || base == ManifestExt
|
||||
}
|
||||
|
||||
func preProcessBundle(loader DirectoryLoader, skipVerify bool, sizeLimitBytes int64) (SignaturesConfig, Patch, []*Descriptor, error) {
|
||||
func preProcessBundle(loader DirectoryLoader, skipVerify bool, sizeLimitBytes int64) (*Bundle, []*Descriptor, error) {
|
||||
bundle := &Bundle{}
|
||||
descriptors := []*Descriptor{}
|
||||
var signatures SignaturesConfig
|
||||
var patch Patch
|
||||
|
||||
for {
|
||||
f, err := loader.NextFile()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return signatures, patch, nil, fmt.Errorf("bundle read failed: %w", err)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return bundle, nil, fmt.Errorf("bundle read failed: %w", err)
|
||||
}
|
||||
|
||||
// check for the signatures file
|
||||
if !skipVerify && strings.HasSuffix(f.Path(), SignaturesFile) {
|
||||
isSignaturesFile := strings.HasSuffix(f.Path(), SignaturesFile)
|
||||
|
||||
if !skipVerify && isSignaturesFile {
|
||||
buf, err := readFile(f, sizeLimitBytes)
|
||||
if err != nil {
|
||||
return signatures, patch, nil, err
|
||||
return bundle, nil, err
|
||||
}
|
||||
|
||||
if err := util.NewJSONDecoder(&buf).Decode(&signatures); err != nil {
|
||||
return signatures, patch, nil, fmt.Errorf("bundle load failed on signatures decode: %w", err)
|
||||
if err := util.NewJSONDecoder(&buf).Decode(&bundle.Signatures); err != nil {
|
||||
return bundle, nil, fmt.Errorf("bundle load failed on signatures decode: %w", err)
|
||||
}
|
||||
} else if !strings.HasSuffix(f.Path(), SignaturesFile) {
|
||||
} else if !isSignaturesFile {
|
||||
descriptors = append(descriptors, f)
|
||||
|
||||
if filepath.Base(f.Path()) == patchFile {
|
||||
base := filepath.Base(f.Path())
|
||||
|
||||
if base == patchFile {
|
||||
|
||||
var b bytes.Buffer
|
||||
tee := io.TeeReader(f.reader, &b)
|
||||
@@ -1758,18 +1699,19 @@ func preProcessBundle(loader DirectoryLoader, skipVerify bool, sizeLimitBytes in
|
||||
|
||||
buf, err := readFile(f, sizeLimitBytes)
|
||||
if err != nil {
|
||||
return signatures, patch, nil, err
|
||||
return bundle, nil, err
|
||||
}
|
||||
|
||||
if err := util.NewJSONDecoder(&buf).Decode(&patch); err != nil {
|
||||
return signatures, patch, nil, fmt.Errorf("bundle load failed on patch decode: %w", err)
|
||||
if err := util.NewJSONDecoder(&buf).Decode(&bundle.Patch); err != nil {
|
||||
return bundle, nil, fmt.Errorf("bundle load failed on patch decode: %w", err)
|
||||
}
|
||||
|
||||
f.reader = &b
|
||||
}
|
||||
}
|
||||
}
|
||||
return signatures, patch, descriptors, nil
|
||||
|
||||
return bundle, descriptors, nil
|
||||
}
|
||||
|
||||
func readFile(f *Descriptor, sizeLimitBytes int64) (bytes.Buffer, error) {
|
||||
@@ -1839,7 +1781,3 @@ func fstatFileSize(f *os.File) (int64, error) {
|
||||
}
|
||||
return fileInfo.Size(), nil
|
||||
}
|
||||
|
||||
func normalizePath(p string) string {
|
||||
return filepath.ToSlash(p)
|
||||
}
|
||||
|
||||
+5
-8
@@ -352,12 +352,10 @@ func (t *tarballLoader) NextFile() (*Descriptor, error) {
|
||||
|
||||
for {
|
||||
header, err := t.tr.Next()
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -365,7 +363,6 @@ func (t *tarballLoader) NextFile() (*Descriptor, error) {
|
||||
if header.Typeflag == tar.TypeReg {
|
||||
|
||||
if t.filter != nil {
|
||||
|
||||
if t.filter(filepath.ToSlash(header.Name), header.FileInfo(), getdepth(header.Name, false)) {
|
||||
continue
|
||||
}
|
||||
@@ -504,9 +501,9 @@ func getdepth(path string, isDir bool) int {
|
||||
}
|
||||
|
||||
func getFileStoragePath(path string) (storage.Path, error) {
|
||||
fpath := strings.TrimLeft(normalizePath(filepath.Dir(path)), "/.")
|
||||
fpath := strings.TrimLeft(filepath.ToSlash(filepath.Dir(path)), "/.")
|
||||
if strings.HasSuffix(path, RegoExt) {
|
||||
fpath = strings.Trim(normalizePath(path), "/")
|
||||
fpath = strings.Trim(filepath.ToSlash(path), "/")
|
||||
}
|
||||
|
||||
p, ok := storage.ParsePathEscaped("/" + fpath)
|
||||
|
||||
-3
@@ -1,6 +1,3 @@
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
package bundle
|
||||
|
||||
import (
|
||||
|
||||
+1
-2
@@ -14,7 +14,6 @@ import (
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
@@ -132,5 +131,5 @@ func encodePrimitive(v any) []byte {
|
||||
encoder := json.NewEncoder(&buf)
|
||||
encoder.SetEscapeHTML(false)
|
||||
_ = encoder.Encode(v)
|
||||
return []byte(strings.Trim(buf.String(), "\n"))
|
||||
return bytes.Trim(buf.Bytes(), "\n")
|
||||
}
|
||||
|
||||
+7
-3
@@ -571,12 +571,11 @@ func doDFS(obj map[string]json.RawMessage, path string, roots []string) error {
|
||||
}
|
||||
|
||||
for key := range obj {
|
||||
|
||||
newPath := filepath.Join(strings.Trim(path, "/"), key)
|
||||
|
||||
// Note: filepath.Join can return paths with '\' separators, always use
|
||||
// filepath.ToSlash to keep them normalized.
|
||||
newPath = strings.TrimLeft(normalizePath(newPath), "/.")
|
||||
newPath = strings.TrimLeft(filepath.ToSlash(newPath), "/.")
|
||||
|
||||
contains := false
|
||||
prefix := false
|
||||
@@ -971,7 +970,7 @@ func compileModules(compiler *ast.Compiler, m metrics.Metrics, bundles map[strin
|
||||
m.Timer(metrics.RegoModuleCompile).Start()
|
||||
defer m.Timer(metrics.RegoModuleCompile).Stop()
|
||||
|
||||
modules := map[string]*ast.Module{}
|
||||
modules := make(map[string]*ast.Module, len(compiler.Modules)+len(extraModules)+len(bundles))
|
||||
|
||||
// preserve any modules already on the compiler
|
||||
maps.Copy(modules, compiler.Modules)
|
||||
@@ -1191,17 +1190,20 @@ func applyPatches(ctx context.Context, store storage.Store, txn storage.Transact
|
||||
// Helpers for the older single (unnamed) bundle style manifest storage.
|
||||
|
||||
// LegacyManifestStoragePath is the older unnamed bundle path for manifests to be stored.
|
||||
//
|
||||
// Deprecated: Use ManifestStoragePath and named bundles instead.
|
||||
var legacyManifestStoragePath = storage.MustParsePath("/system/bundle/manifest")
|
||||
var legacyRevisionStoragePath = append(legacyManifestStoragePath, "revision")
|
||||
|
||||
// LegacyWriteManifestToStore will write the bundle manifest to the older single (unnamed) bundle manifest location.
|
||||
//
|
||||
// Deprecated: Use WriteManifestToStore and named bundles instead.
|
||||
func LegacyWriteManifestToStore(ctx context.Context, store storage.Store, txn storage.Transaction, manifest Manifest) error {
|
||||
return write(ctx, store, txn, legacyManifestStoragePath, manifest)
|
||||
}
|
||||
|
||||
// LegacyEraseManifestFromStore will erase the bundle manifest from the older single (unnamed) bundle manifest location.
|
||||
//
|
||||
// Deprecated: Use WriteManifestToStore and named bundles instead.
|
||||
func LegacyEraseManifestFromStore(ctx context.Context, store storage.Store, txn storage.Transaction) error {
|
||||
err := store.Write(ctx, txn, storage.RemoveOp, legacyManifestStoragePath, nil)
|
||||
@@ -1212,12 +1214,14 @@ func LegacyEraseManifestFromStore(ctx context.Context, store storage.Store, txn
|
||||
}
|
||||
|
||||
// LegacyReadRevisionFromStore will read the bundle manifest revision from the older single (unnamed) bundle manifest location.
|
||||
//
|
||||
// Deprecated: Use ReadBundleRevisionFromStore and named bundles instead.
|
||||
func LegacyReadRevisionFromStore(ctx context.Context, store storage.Store, txn storage.Transaction) (string, error) {
|
||||
return readRevisionFromStore(ctx, store, txn, legacyRevisionStoragePath)
|
||||
}
|
||||
|
||||
// ActivateLegacy calls Activate for the bundles but will also write their manifest to the older unnamed store location.
|
||||
//
|
||||
// Deprecated: Use Activate with named bundles instead.
|
||||
func ActivateLegacy(opts *ActivateOpts) error {
|
||||
opts.legacy = true
|
||||
|
||||
-4
@@ -144,10 +144,6 @@ func verifyJWTSignature(token string, bvc *VerificationConfig) (*DecodedSignatur
|
||||
|
||||
// Because we want to fallback to ds.KeyID when we can't find the
|
||||
// keyID, we need to parse the payload here already.
|
||||
//
|
||||
// (lestrrat) Whoa, you're going to trust the payload before you
|
||||
// verify the signature? Even if it's for backwrds compatibility,
|
||||
// Is this OK?
|
||||
decoder := base64.RawURLEncoding
|
||||
payload := make([]byte, decoder.DecodedLen(len(payloadb64)))
|
||||
if _, err := decoder.Decode(payload, payloadb64); err != nil {
|
||||
|
||||
-3
@@ -2,9 +2,6 @@
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
package capabilities
|
||||
|
||||
import (
|
||||
|
||||
-393
@@ -1,393 +0,0 @@
|
||||
// Copyright 2018 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package config implements OPA configuration file parsing and validation.
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/open-policy-agent/opa/internal/ref"
|
||||
"github.com/open-policy-agent/opa/v1/ast"
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
"github.com/open-policy-agent/opa/v1/version"
|
||||
)
|
||||
|
||||
// ServerConfig represents the different server configuration options.
|
||||
type ServerConfig struct {
|
||||
Metrics json.RawMessage `json:"metrics,omitempty"`
|
||||
|
||||
Encoding json.RawMessage `json:"encoding,omitempty"`
|
||||
Decoding json.RawMessage `json:"decoding,omitempty"`
|
||||
}
|
||||
|
||||
// Clone creates a deep copy of ServerConfig.
|
||||
func (s *ServerConfig) Clone() *ServerConfig {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
clone := &ServerConfig{}
|
||||
|
||||
if s.Encoding != nil {
|
||||
clone.Encoding = make(json.RawMessage, len(s.Encoding))
|
||||
copy(clone.Encoding, s.Encoding)
|
||||
}
|
||||
if s.Decoding != nil {
|
||||
clone.Decoding = make(json.RawMessage, len(s.Decoding))
|
||||
copy(clone.Decoding, s.Decoding)
|
||||
}
|
||||
if s.Metrics != nil {
|
||||
clone.Metrics = make(json.RawMessage, len(s.Metrics))
|
||||
copy(clone.Metrics, s.Metrics)
|
||||
}
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
// StorageConfig represents Config's storage options.
|
||||
type StorageConfig struct {
|
||||
Disk json.RawMessage `json:"disk,omitempty"`
|
||||
}
|
||||
|
||||
// Clone creates a deep copy of StorageConfig.
|
||||
func (s *StorageConfig) Clone() *StorageConfig {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
clone := &StorageConfig{}
|
||||
|
||||
if s.Disk != nil {
|
||||
clone.Disk = make(json.RawMessage, len(s.Disk))
|
||||
copy(clone.Disk, s.Disk)
|
||||
}
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
// Config represents the configuration file that OPA can be started with.
|
||||
type Config struct {
|
||||
Services json.RawMessage `json:"services,omitempty"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
Discovery json.RawMessage `json:"discovery,omitempty"`
|
||||
Bundle json.RawMessage `json:"bundle,omitempty"` // Deprecated: Use `bundles` instead
|
||||
Bundles json.RawMessage `json:"bundles,omitempty"`
|
||||
DecisionLogs json.RawMessage `json:"decision_logs,omitempty"`
|
||||
Status json.RawMessage `json:"status,omitempty"`
|
||||
Plugins map[string]json.RawMessage `json:"plugins,omitempty"`
|
||||
Keys json.RawMessage `json:"keys,omitempty"`
|
||||
DefaultDecision *string `json:"default_decision,omitempty"`
|
||||
DefaultAuthorizationDecision *string `json:"default_authorization_decision,omitempty"`
|
||||
Caching json.RawMessage `json:"caching,omitempty"`
|
||||
NDBuiltinCache bool `json:"nd_builtin_cache,omitempty"`
|
||||
PersistenceDirectory *string `json:"persistence_directory,omitempty"`
|
||||
DistributedTracing json.RawMessage `json:"distributed_tracing,omitempty"`
|
||||
Server *ServerConfig `json:"server,omitempty"`
|
||||
Storage *StorageConfig `json:"storage,omitempty"`
|
||||
Extra map[string]json.RawMessage `json:"-"`
|
||||
}
|
||||
|
||||
// ParseConfig returns a valid Config object with defaults injected. The id
|
||||
// and version parameters will be set in the labels map.
|
||||
func ParseConfig(raw []byte, id string) (*Config, error) {
|
||||
// NOTE(sr): based on https://stackoverflow.com/a/33499066/993018
|
||||
var result Config
|
||||
objValue := reflect.ValueOf(&result).Elem()
|
||||
knownFields := map[string]reflect.Value{}
|
||||
for i := 0; i != objValue.NumField(); i++ {
|
||||
jsonName := strings.Split(objValue.Type().Field(i).Tag.Get("json"), ",")[0]
|
||||
knownFields[jsonName] = objValue.Field(i)
|
||||
}
|
||||
|
||||
if err := util.Unmarshal(raw, &result.Extra); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for key, chunk := range result.Extra {
|
||||
if field, found := knownFields[key]; found {
|
||||
if err := util.Unmarshal(chunk, field.Addr().Interface()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
delete(result.Extra, key)
|
||||
}
|
||||
}
|
||||
if len(result.Extra) == 0 {
|
||||
result.Extra = nil
|
||||
}
|
||||
return &result, result.validateAndInjectDefaults(id)
|
||||
}
|
||||
|
||||
// PluginNames returns a sorted list of names of enabled plugins.
|
||||
func (c Config) PluginNames() (result []string) {
|
||||
if c.Bundle != nil || c.Bundles != nil {
|
||||
result = append(result, "bundles")
|
||||
}
|
||||
if c.Status != nil {
|
||||
result = append(result, "status")
|
||||
}
|
||||
if c.DecisionLogs != nil {
|
||||
result = append(result, "decision_logs")
|
||||
}
|
||||
for name := range c.Plugins {
|
||||
result = append(result, name)
|
||||
}
|
||||
sort.Strings(result)
|
||||
return result
|
||||
}
|
||||
|
||||
// PluginsEnabled returns true if one or more plugin features are enabled.
|
||||
//
|
||||
// Deprecated: Use PluginNames instead.
|
||||
func (c Config) PluginsEnabled() bool {
|
||||
return c.Bundle != nil || c.Bundles != nil || c.DecisionLogs != nil || c.Status != nil || len(c.Plugins) > 0
|
||||
}
|
||||
|
||||
// DefaultDecisionRef returns the default decision as a reference.
|
||||
func (c Config) DefaultDecisionRef() ast.Ref {
|
||||
r, _ := ref.ParseDataPath(*c.DefaultDecision)
|
||||
return r
|
||||
}
|
||||
|
||||
// DefaultAuthorizationDecisionRef returns the default authorization decision
|
||||
// as a reference.
|
||||
func (c Config) DefaultAuthorizationDecisionRef() ast.Ref {
|
||||
r, _ := ref.ParseDataPath(*c.DefaultAuthorizationDecision)
|
||||
return r
|
||||
}
|
||||
|
||||
// NDBuiltinCacheEnabled returns if the ND builtins cache should be used.
|
||||
func (c Config) NDBuiltinCacheEnabled() bool {
|
||||
return c.NDBuiltinCache
|
||||
}
|
||||
|
||||
// GetPersistenceDirectory returns the configured persistence directory, or $PWD/.opa if none is configured
|
||||
func (c Config) GetPersistenceDirectory() (string, error) {
|
||||
if c.PersistenceDirectory == nil {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(pwd, ".opa"), nil
|
||||
}
|
||||
return *c.PersistenceDirectory, nil
|
||||
}
|
||||
|
||||
// ActiveConfig returns OPA's active configuration
|
||||
// with the credentials and crypto keys removed
|
||||
func (c *Config) ActiveConfig() (any, error) {
|
||||
bs, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result map[string]any
|
||||
if err := util.UnmarshalJSON(bs, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, e := range c.Extra {
|
||||
var v any
|
||||
if err := util.UnmarshalJSON(e, &v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[k] = v
|
||||
}
|
||||
|
||||
if err := removeServiceCredentials(result["services"]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := removeCryptoKeys(result["keys"]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Clone creates a deep copy of the Config struct
|
||||
func (c *Config) Clone() *Config {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
clone := &Config{
|
||||
NDBuiltinCache: c.NDBuiltinCache,
|
||||
Server: c.Server.Clone(),
|
||||
Storage: c.Storage.Clone(),
|
||||
Labels: maps.Clone(c.Labels),
|
||||
}
|
||||
|
||||
if c.Services != nil {
|
||||
clone.Services = make(json.RawMessage, len(c.Services))
|
||||
copy(clone.Services, c.Services)
|
||||
}
|
||||
if c.Discovery != nil {
|
||||
clone.Discovery = make(json.RawMessage, len(c.Discovery))
|
||||
copy(clone.Discovery, c.Discovery)
|
||||
}
|
||||
if c.Bundle != nil {
|
||||
clone.Bundle = make(json.RawMessage, len(c.Bundle))
|
||||
copy(clone.Bundle, c.Bundle)
|
||||
}
|
||||
if c.Bundles != nil {
|
||||
clone.Bundles = make(json.RawMessage, len(c.Bundles))
|
||||
copy(clone.Bundles, c.Bundles)
|
||||
}
|
||||
if c.DecisionLogs != nil {
|
||||
clone.DecisionLogs = make(json.RawMessage, len(c.DecisionLogs))
|
||||
copy(clone.DecisionLogs, c.DecisionLogs)
|
||||
}
|
||||
if c.Status != nil {
|
||||
clone.Status = make(json.RawMessage, len(c.Status))
|
||||
copy(clone.Status, c.Status)
|
||||
}
|
||||
if c.Keys != nil {
|
||||
clone.Keys = make(json.RawMessage, len(c.Keys))
|
||||
copy(clone.Keys, c.Keys)
|
||||
}
|
||||
if c.Caching != nil {
|
||||
clone.Caching = make(json.RawMessage, len(c.Caching))
|
||||
copy(clone.Caching, c.Caching)
|
||||
}
|
||||
if c.DistributedTracing != nil {
|
||||
clone.DistributedTracing = make(json.RawMessage, len(c.DistributedTracing))
|
||||
copy(clone.DistributedTracing, c.DistributedTracing)
|
||||
}
|
||||
|
||||
if c.DefaultDecision != nil {
|
||||
s := *c.DefaultDecision
|
||||
clone.DefaultDecision = &s
|
||||
}
|
||||
if c.DefaultAuthorizationDecision != nil {
|
||||
s := *c.DefaultAuthorizationDecision
|
||||
clone.DefaultAuthorizationDecision = &s
|
||||
}
|
||||
if c.PersistenceDirectory != nil {
|
||||
s := *c.PersistenceDirectory
|
||||
clone.PersistenceDirectory = &s
|
||||
}
|
||||
|
||||
if c.Plugins != nil {
|
||||
clone.Plugins = make(map[string]json.RawMessage, len(c.Plugins))
|
||||
for k, v := range c.Plugins {
|
||||
if v != nil {
|
||||
clone.Plugins[k] = make(json.RawMessage, len(v))
|
||||
copy(clone.Plugins[k], v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if c.Extra != nil {
|
||||
clone.Extra = make(map[string]json.RawMessage, len(c.Extra))
|
||||
for k, v := range c.Extra {
|
||||
if v != nil {
|
||||
clone.Extra[k] = make(json.RawMessage, len(v))
|
||||
copy(clone.Extra[k], v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
func (c *Config) validateAndInjectDefaults(id string) error {
|
||||
if c.DefaultDecision == nil {
|
||||
s := defaultDecisionPath
|
||||
c.DefaultDecision = &s
|
||||
}
|
||||
|
||||
_, err := ref.ParseDataPath(*c.DefaultDecision)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.DefaultAuthorizationDecision == nil {
|
||||
s := defaultAuthorizationDecisionPath
|
||||
c.DefaultAuthorizationDecision = &s
|
||||
}
|
||||
|
||||
_, err = ref.ParseDataPath(*c.DefaultAuthorizationDecision)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Labels == nil {
|
||||
c.Labels = map[string]string{}
|
||||
}
|
||||
|
||||
c.Labels["id"] = id
|
||||
c.Labels["version"] = version.Version
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeServiceCredentials(x any) error {
|
||||
switch x := x.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
case []any:
|
||||
for _, v := range x {
|
||||
err := removeKey(v, "credentials")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case map[string]any:
|
||||
for _, v := range x {
|
||||
err := removeKey(v, "credentials")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("illegal service config type: %T", x)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeCryptoKeys(x any) error {
|
||||
switch x := x.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
case map[string]any:
|
||||
for _, v := range x {
|
||||
err := removeKey(v, "key", "private_key")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("illegal keys config type: %T", x)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeKey(x any, keys ...string) error {
|
||||
val, ok := x.(map[string]any)
|
||||
if !ok {
|
||||
return errors.New("type assertion error")
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
delete(val, key)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
defaultDecisionPath = "/system/main"
|
||||
defaultAuthorizationDecisionPath = "/system/authz/allow"
|
||||
)
|
||||
+93
-7
@@ -9,7 +9,6 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -27,11 +26,8 @@ import (
|
||||
const defaultLocationFile = "__format_default__"
|
||||
|
||||
var (
|
||||
elseVar ast.Value = ast.Var("else")
|
||||
|
||||
expandedConst = ast.NewBody(ast.NewExpr(ast.InternedTerm(true)))
|
||||
commentsSlicePool = util.NewSlicePool[*ast.Comment](50)
|
||||
varRegexp = regexp.MustCompile("^[[:alpha:]_][[:alpha:][:digit:]_]*$")
|
||||
)
|
||||
|
||||
// Opts lets you control the code formatting via `AstWithOpts()`.
|
||||
@@ -732,7 +728,7 @@ func (w *writer) writeElse(rule *ast.Rule, comments []*ast.Comment) ([]*ast.Comm
|
||||
|
||||
rule.Else.Head.Name = "else" // NOTE(sr): whaaat
|
||||
|
||||
elseHeadReference := ast.NewTerm(elseVar) // construct a reference for the term
|
||||
elseHeadReference := ast.VarTerm("else") // construct a reference for the term
|
||||
elseHeadReference.Location = rule.Else.Head.Location // and set the location to match the rule location
|
||||
|
||||
rule.Else.Head.Reference = ast.Ref{elseHeadReference}
|
||||
@@ -1284,6 +1280,11 @@ func (w *writer) writeTermParens(parens bool, term *ast.Term, comments []*ast.Co
|
||||
}
|
||||
|
||||
}
|
||||
case *ast.TemplateString:
|
||||
comments, err = w.writeTemplateString(x, comments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case ast.Var:
|
||||
w.write(w.formatVar(x))
|
||||
case ast.Call:
|
||||
@@ -1301,6 +1302,91 @@ func (w *writer) writeTermParens(parens bool, term *ast.Term, comments []*ast.Co
|
||||
return comments, nil
|
||||
}
|
||||
|
||||
func (w *writer) writeTemplateString(ts *ast.TemplateString, comments []*ast.Comment) ([]*ast.Comment, error) {
|
||||
w.write("$")
|
||||
if ts.MultiLine {
|
||||
w.write("`")
|
||||
} else {
|
||||
w.write(`"`)
|
||||
}
|
||||
|
||||
for i, p := range ts.Parts {
|
||||
switch x := p.(type) {
|
||||
case *ast.Expr:
|
||||
w.write("{")
|
||||
w.up()
|
||||
|
||||
if w.beforeEnd != nil {
|
||||
// We have a comment on the same line as the opening template-expression brace '{'
|
||||
w.endLine()
|
||||
w.startLine()
|
||||
} else {
|
||||
// We might have comments to write; the first of which should be on the same line as the opening template-expression brace '{'
|
||||
before, _, _ := partitionComments(comments, x.Location)
|
||||
if len(before) > 0 {
|
||||
w.write(" ")
|
||||
w.inline = true
|
||||
if err := w.writeComments(before); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
comments = comments[len(before):]
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
comments, err = w.writeExpr(x, comments)
|
||||
if err != nil {
|
||||
return comments, err
|
||||
}
|
||||
|
||||
// write trailing comments
|
||||
if i+1 < len(ts.Parts) {
|
||||
before, _, _ := partitionComments(comments, ts.Parts[i+1].Loc())
|
||||
if len(before) > 0 {
|
||||
w.endLine()
|
||||
if err := w.writeComments(before); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
comments = comments[len(before):]
|
||||
w.startLine()
|
||||
}
|
||||
}
|
||||
|
||||
w.write("}")
|
||||
|
||||
if err := w.down(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *ast.Term:
|
||||
if s, ok := x.Value.(ast.String); ok {
|
||||
if ts.MultiLine {
|
||||
w.write(ast.EscapeTemplateStringStringPart(string(s)))
|
||||
} else {
|
||||
str := ast.EscapeTemplateStringStringPart(s.String())
|
||||
w.write(str[1 : len(str)-1])
|
||||
}
|
||||
} else {
|
||||
s := x.String()
|
||||
s = strings.TrimPrefix(s, "\"")
|
||||
s = strings.TrimSuffix(s, "\"")
|
||||
w.write(s)
|
||||
}
|
||||
default:
|
||||
w.write("<invalid>")
|
||||
}
|
||||
}
|
||||
|
||||
if ts.MultiLine {
|
||||
w.write("`")
|
||||
} else {
|
||||
w.write(`"`)
|
||||
}
|
||||
|
||||
return comments, nil
|
||||
}
|
||||
|
||||
func (w *writer) writeRef(x ast.Ref, comments []*ast.Comment) ([]*ast.Comment, error) {
|
||||
if len(x) > 0 {
|
||||
parens := false
|
||||
@@ -1353,7 +1439,7 @@ func (w *writer) writeRefStringPath(s ast.String, l *ast.Location) {
|
||||
}
|
||||
|
||||
func (w *writer) shouldBracketRefTerm(s string, l *ast.Location) bool {
|
||||
if !varRegexp.MatchString(s) {
|
||||
if !ast.IsVarCompatibleString(s) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1931,7 +2017,7 @@ func partitionComments(comments []*ast.Comment, l *ast.Location) ([]*ast.Comment
|
||||
var at *ast.Comment
|
||||
|
||||
before := make([]*ast.Comment, 0, numBefore)
|
||||
after := comments[0 : 0 : len(comments)-numBefore]
|
||||
after := make([]*ast.Comment, 0, numAfter)
|
||||
|
||||
for _, c := range comments {
|
||||
switch cmp := c.Location.Row - l.Row; {
|
||||
|
||||
-97
@@ -1,97 +0,0 @@
|
||||
// Copyright 2023 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/config"
|
||||
topdown_cache "github.com/open-policy-agent/opa/v1/topdown/cache"
|
||||
)
|
||||
|
||||
// Hook is a hook to be called in some select places in OPA's operation.
|
||||
//
|
||||
// The base Hook interface is any, and wherever a hook can occur, the calling code
|
||||
// will check if your hook implements an appropriate interface. If so, your hook
|
||||
// is called.
|
||||
//
|
||||
// This allows you to only hook in to behavior you care about, and it allows the
|
||||
// OPA to add more hooks in the future.
|
||||
//
|
||||
// All hook interfaces in this package have Hook in the name. Hooks must be safe
|
||||
// for concurrent use. It is expected that hooks are fast; if a hook needs to take
|
||||
// time, then copy what you need and ensure the hook is async.
|
||||
//
|
||||
// When multiple instances of a hook are provided, they are all going to be executed
|
||||
// in an unspecified order (it's a map-range call underneath). If you need hooks to
|
||||
// be run in order, you can wrap them into another hook, and configure that one.
|
||||
type Hook any
|
||||
|
||||
// Hooks is the type used for every struct in OPA that can work with hooks.
|
||||
type Hooks struct {
|
||||
m map[Hook]struct{} // we are NOT providing a stable invocation ordering
|
||||
}
|
||||
|
||||
// New creates a new instance of Hooks.
|
||||
func New(hs ...Hook) Hooks {
|
||||
h := Hooks{m: make(map[Hook]struct{}, len(hs))}
|
||||
for i := range hs {
|
||||
h.m[hs[i]] = struct{}{}
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (hs Hooks) Each(fn func(Hook)) {
|
||||
for h := range hs.m {
|
||||
fn(h)
|
||||
}
|
||||
}
|
||||
|
||||
func (hs Hooks) Len() int {
|
||||
return len(hs.m)
|
||||
}
|
||||
|
||||
// ConfigHook allows inspecting or rewriting the configuration when the plugin
|
||||
// manager is processing it.
|
||||
// Note that this hook is not run when the plugin manager is reconfigured. This
|
||||
// usually only happens when there's a new config from a discovery bundle, and
|
||||
// for processing _that_, there's `ConfigDiscoveryHook`.
|
||||
type ConfigHook interface {
|
||||
OnConfig(context.Context, *config.Config) (*config.Config, error)
|
||||
}
|
||||
|
||||
// ConfigHook allows inspecting or rewriting the discovered configuration when
|
||||
// the discovery plugin is processing it.
|
||||
type ConfigDiscoveryHook interface {
|
||||
OnConfigDiscovery(context.Context, *config.Config) (*config.Config, error)
|
||||
}
|
||||
|
||||
// InterQueryCacheHook allows access to the server's inter-query cache instance.
|
||||
// It's useful for out-of-tree handlers that also need to evaluate something.
|
||||
// Using this hook, they can share the caches with the rest of OPA.
|
||||
type InterQueryCacheHook interface {
|
||||
OnInterQueryCache(context.Context, topdown_cache.InterQueryCache) error
|
||||
}
|
||||
|
||||
// InterQueryValueCacheHook allows access to the server's inter-query value cache
|
||||
// instance.
|
||||
type InterQueryValueCacheHook interface {
|
||||
OnInterQueryValueCache(context.Context, topdown_cache.InterQueryValueCache) error
|
||||
}
|
||||
|
||||
func (hs Hooks) Validate() error {
|
||||
for h := range hs.m {
|
||||
switch h.(type) {
|
||||
case InterQueryCacheHook,
|
||||
InterQueryValueCacheHook,
|
||||
ConfigHook,
|
||||
ConfigDiscoveryHook: // OK
|
||||
default:
|
||||
return fmt.Errorf("unknown hook type %T", h)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+4
-2
@@ -495,6 +495,7 @@ func loadOneSchema(path string) (any, error) {
|
||||
}
|
||||
|
||||
// All returns a Result object loaded (recursively) from the specified paths.
|
||||
//
|
||||
// Deprecated: Use FileLoader.Filtered() instead.
|
||||
func All(paths []string) (*Result, error) {
|
||||
return NewFileLoader().Filtered(paths, nil)
|
||||
@@ -503,6 +504,7 @@ func All(paths []string) (*Result, error) {
|
||||
// Filtered returns a Result object loaded (recursively) from the specified
|
||||
// paths while applying the given filters. If any filter returns true, the
|
||||
// file/directory is excluded.
|
||||
//
|
||||
// Deprecated: Use FileLoader.Filtered() instead.
|
||||
func Filtered(paths []string, filter Filter) (*Result, error) {
|
||||
return NewFileLoader().Filtered(paths, filter)
|
||||
@@ -511,6 +513,7 @@ func Filtered(paths []string, filter Filter) (*Result, error) {
|
||||
// AsBundle loads a path as a bundle. If it is a single file
|
||||
// it will be treated as a normal tarball bundle. If a directory
|
||||
// is supplied it will be loaded as an unzipped bundle tree.
|
||||
//
|
||||
// Deprecated: Use FileLoader.AsBundle() instead.
|
||||
func AsBundle(path string) (*bundle.Bundle, error) {
|
||||
return NewFileLoader().AsBundle(path)
|
||||
@@ -631,11 +634,10 @@ func (l *Result) mergeDocument(path string, doc any) error {
|
||||
}
|
||||
|
||||
func (l *Result) withParent(p string) *Result {
|
||||
path := append(l.path, p)
|
||||
return &Result{
|
||||
Documents: l.Documents,
|
||||
Modules: l.Modules,
|
||||
path: path,
|
||||
path: append(l.path, p),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-1195
File diff suppressed because it is too large
Load Diff
-1211
File diff suppressed because it is too large
Load Diff
-1088
File diff suppressed because it is too large
Load Diff
-287
@@ -1,287 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
azureIMDSEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token"
|
||||
defaultAPIVersion = "2018-02-01"
|
||||
defaultResource = "https://storage.azure.com/"
|
||||
timeout = 5 * time.Second
|
||||
defaultAPIVersionForAppServiceMsi = "2019-08-01"
|
||||
defaultKeyVaultAPIVersion = "7.4"
|
||||
)
|
||||
|
||||
// azureManagedIdentitiesToken holds a token for managed identities for Azure resources
|
||||
type azureManagedIdentitiesToken struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn string `json:"expires_in"`
|
||||
ExpiresOn string `json:"expires_on"`
|
||||
NotBefore string `json:"not_before"`
|
||||
Resource string `json:"resource"`
|
||||
TokenType string `json:"token_type"`
|
||||
}
|
||||
|
||||
// azureManagedIdentitiesError represents an error fetching an azureManagedIdentitiesToken
|
||||
type azureManagedIdentitiesError struct {
|
||||
Err string `json:"error"`
|
||||
Description string `json:"error_description"`
|
||||
Endpoint string
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
func (e *azureManagedIdentitiesError) Error() string {
|
||||
return fmt.Sprintf("%v %s retrieving azure token from %s: %s", e.StatusCode, e.Err, e.Endpoint, e.Description)
|
||||
}
|
||||
|
||||
// azureManagedIdentitiesAuthPlugin uses an azureManagedIdentitiesToken.AccessToken for bearer authorization
|
||||
type azureManagedIdentitiesAuthPlugin struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
APIVersion string `json:"api_version"`
|
||||
Resource string `json:"resource"`
|
||||
ObjectID string `json:"object_id"`
|
||||
ClientID string `json:"client_id"`
|
||||
MiResID string `json:"mi_res_id"`
|
||||
UseAppServiceMsi bool `json:"use_app_service_msi,omitempty"`
|
||||
}
|
||||
|
||||
func (ap *azureManagedIdentitiesAuthPlugin) setDefaults() {
|
||||
if ap.Endpoint == "" {
|
||||
identityEndpoint := os.Getenv("IDENTITY_ENDPOINT")
|
||||
if identityEndpoint != "" {
|
||||
ap.UseAppServiceMsi = true
|
||||
ap.Endpoint = identityEndpoint
|
||||
} else {
|
||||
ap.Endpoint = azureIMDSEndpoint
|
||||
}
|
||||
}
|
||||
|
||||
if ap.Resource == "" {
|
||||
ap.Resource = defaultResource
|
||||
}
|
||||
|
||||
if ap.APIVersion == "" {
|
||||
if ap.UseAppServiceMsi {
|
||||
ap.APIVersion = defaultAPIVersionForAppServiceMsi
|
||||
} else {
|
||||
ap.APIVersion = defaultAPIVersion
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (ap *azureManagedIdentitiesAuthPlugin) NewClient(c Config) (*http.Client, error) {
|
||||
if c.Type == "oci" {
|
||||
return nil, errors.New("azure managed identities auth: OCI service not supported")
|
||||
}
|
||||
ap.setDefaults()
|
||||
t, err := DefaultTLSConfig(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return DefaultRoundTripperClient(t, *c.ResponseHeaderTimeoutSeconds), nil
|
||||
}
|
||||
|
||||
func (ap *azureManagedIdentitiesAuthPlugin) Prepare(req *http.Request) error {
|
||||
token, err := azureManagedIdentitiesTokenRequest(
|
||||
ap.Endpoint, ap.APIVersion, ap.Resource,
|
||||
ap.ObjectID, ap.ClientID, ap.MiResID,
|
||||
ap.UseAppServiceMsi,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", "Bearer "+token.AccessToken)
|
||||
return nil
|
||||
}
|
||||
|
||||
// azureManagedIdentitiesTokenRequest fetches an azureManagedIdentitiesToken
|
||||
func azureManagedIdentitiesTokenRequest(
|
||||
endpoint, apiVersion, resource, objectID, clientID, miResID string,
|
||||
useAppServiceMsi bool,
|
||||
) (azureManagedIdentitiesToken, error) {
|
||||
var token azureManagedIdentitiesToken
|
||||
e := buildAzureManagedIdentitiesRequestPath(endpoint, apiVersion, resource, objectID, clientID, miResID)
|
||||
|
||||
request, err := http.NewRequest("GET", e, nil)
|
||||
if err != nil {
|
||||
return token, err
|
||||
}
|
||||
if useAppServiceMsi {
|
||||
identityHeader := os.Getenv("IDENTITY_HEADER")
|
||||
if identityHeader == "" {
|
||||
return token, errors.New("azure managed identities auth: IDENTITY_HEADER env var not found")
|
||||
}
|
||||
request.Header.Add("x-identity-header", identityHeader)
|
||||
} else {
|
||||
request.Header.Add("Metadata", "true")
|
||||
}
|
||||
|
||||
httpClient := http.Client{Timeout: timeout}
|
||||
response, err := httpClient.Do(request)
|
||||
if err != nil {
|
||||
return token, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
data, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return token, err
|
||||
}
|
||||
|
||||
if s := response.StatusCode; s != http.StatusOK {
|
||||
var azureError azureManagedIdentitiesError
|
||||
err = json.Unmarshal(data, &azureError)
|
||||
if err != nil {
|
||||
return token, err
|
||||
}
|
||||
|
||||
azureError.Endpoint = e
|
||||
azureError.StatusCode = s
|
||||
return token, &azureError
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, &token)
|
||||
if err != nil {
|
||||
return token, err
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// buildAzureManagedIdentitiesRequestPath constructs the request URL for an Azure managed identities token request
|
||||
func buildAzureManagedIdentitiesRequestPath(
|
||||
endpoint, apiVersion, resource, objectID, clientID, miResID string,
|
||||
) string {
|
||||
params := url.Values{
|
||||
"api-version": []string{apiVersion},
|
||||
"resource": []string{resource},
|
||||
}
|
||||
|
||||
if objectID != "" {
|
||||
params.Add("object_id", objectID)
|
||||
}
|
||||
|
||||
if clientID != "" {
|
||||
params.Add("client_id", clientID)
|
||||
}
|
||||
|
||||
if miResID != "" {
|
||||
params.Add("mi_res_id", miResID)
|
||||
}
|
||||
|
||||
return endpoint + "?" + params.Encode()
|
||||
}
|
||||
|
||||
type azureKeyVaultSignPlugin struct {
|
||||
config azureKeyVaultConfig
|
||||
tokener func() (string, error)
|
||||
}
|
||||
|
||||
func newKeyVaultSignPlugin(ap *azureManagedIdentitiesAuthPlugin, cfg *azureKeyVaultConfig) *azureKeyVaultSignPlugin {
|
||||
resp := &azureKeyVaultSignPlugin{
|
||||
tokener: func() (string, error) {
|
||||
resp, err := azureManagedIdentitiesTokenRequest(
|
||||
ap.Endpoint,
|
||||
ap.APIVersion,
|
||||
cfg.URL.String(),
|
||||
ap.ObjectID,
|
||||
ap.ClientID,
|
||||
ap.MiResID,
|
||||
ap.UseAppServiceMsi)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.AccessToken, nil
|
||||
},
|
||||
config: *cfg,
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
func (akv *azureKeyVaultSignPlugin) setDefaults() {
|
||||
if akv.config.APIVersion == "" {
|
||||
akv.config.APIVersion = defaultKeyVaultAPIVersion
|
||||
}
|
||||
}
|
||||
|
||||
type kvRequest struct {
|
||||
Alg string `json:"alg"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type kvResponse struct {
|
||||
KID string `json:"kid"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// SignDigest() uses the Microsoft keyvault rest api to sign a byte digest
|
||||
// https://learn.microsoft.com/en-us/rest/api/keyvault/keys/sign/sign
|
||||
func (ap *azureKeyVaultSignPlugin) SignDigest(ctx context.Context, digest []byte) (string, error) {
|
||||
tkn, err := ap.tokener()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if ap.config.URL.Host == "" {
|
||||
return "", errors.New("keyvault host not set")
|
||||
}
|
||||
|
||||
signingURL := ap.config.URL.JoinPath("keys", ap.config.Key, ap.config.KeyVersion, "sign")
|
||||
q := signingURL.Query()
|
||||
q.Set("api-version", ap.config.APIVersion)
|
||||
signingURL.RawQuery = q.Encode()
|
||||
reqBody, err := json.Marshal(kvRequest{
|
||||
Alg: ap.config.Alg,
|
||||
Value: base64.StdEncoding.EncodeToString(digest)})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, signingURL.String(), bytes.NewBuffer(reqBody))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", "Bearer "+tkn)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
if resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
return "", fmt.Errorf("non 200 status code, got: %d. Body: %v", resp.StatusCode, string(b))
|
||||
}
|
||||
return "", fmt.Errorf("non 200 status code from keyvault sign, got: %d", resp.StatusCode)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", errors.New("failed to read keyvault response body")
|
||||
}
|
||||
|
||||
var res kvResponse
|
||||
err = json.Unmarshal(respBytes, &res)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("no valid keyvault response, got: %v", string(respBytes))
|
||||
}
|
||||
|
||||
return res.Value, nil
|
||||
}
|
||||
-173
@@ -1,173 +0,0 @@
|
||||
// Copyright 2020 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultGCPMetadataEndpoint = "http://metadata.google.internal"
|
||||
defaultAccessTokenPath = "/computeMetadata/v1/instance/service-accounts/default/token"
|
||||
defaultIdentityTokenPath = "/computeMetadata/v1/instance/service-accounts/default/identity"
|
||||
)
|
||||
|
||||
// AccessToken holds a GCP access token.
|
||||
type AccessToken struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn int64 `json:"expires_in"`
|
||||
TokenType string `json:"token_type"`
|
||||
}
|
||||
|
||||
type gcpMetadataError struct {
|
||||
err error
|
||||
endpoint string
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func (e *gcpMetadataError) Error() string {
|
||||
return fmt.Sprintf("error retrieving gcp ID token from %s %d: %v", e.endpoint, e.statusCode, e.err)
|
||||
}
|
||||
|
||||
func (e *gcpMetadataError) Unwrap() error { return e.err }
|
||||
|
||||
var (
|
||||
errGCPMetadataNotFound = errors.New("not found")
|
||||
errGCPMetadataInvalidRequest = errors.New("invalid request")
|
||||
errGCPMetadataUnexpected = errors.New("unexpected error")
|
||||
)
|
||||
|
||||
// gcpMetadataAuthPlugin represents authentication via GCP metadata service.
|
||||
type gcpMetadataAuthPlugin struct {
|
||||
AccessTokenPath string `json:"access_token_path"`
|
||||
Audience string `json:"audience"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
IdentityTokenPath string `json:"identity_token_path"`
|
||||
Scopes []string `json:"scopes"`
|
||||
}
|
||||
|
||||
func (ap *gcpMetadataAuthPlugin) NewClient(c Config) (*http.Client, error) {
|
||||
if ap.Audience == "" && len(ap.Scopes) == 0 {
|
||||
return nil, errors.New("audience or scopes is required when gcp metadata is enabled")
|
||||
}
|
||||
|
||||
if ap.Audience != "" && len(ap.Scopes) > 0 {
|
||||
return nil, errors.New("either audience or scopes can be set, not both, when gcp metadata is enabled")
|
||||
}
|
||||
|
||||
if ap.Endpoint == "" {
|
||||
ap.Endpoint = defaultGCPMetadataEndpoint
|
||||
}
|
||||
|
||||
if ap.AccessTokenPath == "" {
|
||||
ap.AccessTokenPath = defaultAccessTokenPath
|
||||
}
|
||||
|
||||
if ap.IdentityTokenPath == "" {
|
||||
ap.IdentityTokenPath = defaultIdentityTokenPath
|
||||
}
|
||||
|
||||
t, err := DefaultTLSConfig(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return DefaultRoundTripperClient(t, *c.ResponseHeaderTimeoutSeconds), nil
|
||||
}
|
||||
|
||||
func (ap *gcpMetadataAuthPlugin) Prepare(req *http.Request) error {
|
||||
var err error
|
||||
var token string
|
||||
|
||||
if ap.Audience != "" {
|
||||
token, err = identityTokenFromMetadataService(ap.Endpoint, ap.IdentityTokenPath, ap.Audience)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error retrieving identity token from gcp metadata service: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(ap.Scopes) != 0 {
|
||||
token, err = accessTokenFromMetadataService(ap.Endpoint, ap.AccessTokenPath, ap.Scopes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error retrieving access token from gcp metadata service: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %v", token))
|
||||
return nil
|
||||
}
|
||||
|
||||
// accessTokenFromMetadataService returns an access token based on the scopes.
|
||||
func accessTokenFromMetadataService(endpoint, path string, scopes []string) (string, error) {
|
||||
s := strings.Join(scopes, ",")
|
||||
|
||||
e := fmt.Sprintf("%s%s?scopes=%s", endpoint, path, s)
|
||||
|
||||
data, err := gcpMetadataServiceRequest(e)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var accessToken AccessToken
|
||||
err = json.Unmarshal(data, &accessToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return accessToken.AccessToken, nil
|
||||
}
|
||||
|
||||
// identityTokenFromMetadataService returns an identity token based on the audience.
|
||||
func identityTokenFromMetadataService(endpoint, path, audience string) (string, error) {
|
||||
e := fmt.Sprintf("%s%s?audience=%s", endpoint, path, audience)
|
||||
|
||||
data, err := gcpMetadataServiceRequest(e)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func gcpMetadataServiceRequest(endpoint string) ([]byte, error) {
|
||||
request, err := http.NewRequest("GET", endpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request.Header.Add("Metadata-Flavor", "Google")
|
||||
|
||||
timeout := time.Duration(5) * time.Second
|
||||
httpClient := http.Client{Timeout: timeout}
|
||||
|
||||
response, err := httpClient.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
switch s := response.StatusCode; s {
|
||||
case 200:
|
||||
break
|
||||
case 400:
|
||||
return nil, &gcpMetadataError{errGCPMetadataInvalidRequest, endpoint, s}
|
||||
case 404:
|
||||
return nil, &gcpMetadataError{errGCPMetadataNotFound, endpoint, s}
|
||||
default:
|
||||
return nil, &gcpMetadataError{errGCPMetadataUnexpected, endpoint, s}
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user