Initial QSfera import
This commit is contained in:
+147
@@ -0,0 +1,147 @@
|
||||
// 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 bundle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/ast"
|
||||
"github.com/open-policy-agent/opa/v1/bundle"
|
||||
"github.com/open-policy-agent/opa/v1/resolver/wasm"
|
||||
"github.com/open-policy-agent/opa/v1/storage"
|
||||
)
|
||||
|
||||
// LoadWasmResolversFromStore will lookup all Wasm modules from the store along with the
|
||||
// associated bundle manifest configuration and instantiate the respective resolvers.
|
||||
func LoadWasmResolversFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, otherBundles map[string]*bundle.Bundle) ([]*wasm.Resolver, error) {
|
||||
bundleNames, err := bundle.ReadBundleNamesFromStore(ctx, store, txn)
|
||||
if err != nil && !storage.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resolversToLoad []*bundle.WasmModuleFile
|
||||
for _, bundleName := range bundleNames {
|
||||
var wasmResolverConfigs []bundle.WasmResolver
|
||||
rawModules := map[string][]byte{}
|
||||
|
||||
// Save round-tripping the bundle that was just activated
|
||||
if _, ok := otherBundles[bundleName]; ok {
|
||||
wasmResolverConfigs = otherBundles[bundleName].Manifest.WasmResolvers
|
||||
for _, wmf := range otherBundles[bundleName].WasmModules {
|
||||
rawModules[wmf.Path] = wmf.Raw
|
||||
}
|
||||
} else {
|
||||
wasmResolverConfigs, err = bundle.ReadWasmMetadataFromStore(ctx, store, txn, bundleName)
|
||||
if err != nil && !storage.IsNotFound(err) {
|
||||
return nil, fmt.Errorf("failed to read wasm module manifest from store: %s", err)
|
||||
}
|
||||
rawModules, err = bundle.ReadWasmModulesFromStore(ctx, store, txn, bundleName)
|
||||
if err != nil && !storage.IsNotFound(err) {
|
||||
return nil, fmt.Errorf("failed to read wasm modules from store: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
for path, raw := range rawModules {
|
||||
wmf := &bundle.WasmModuleFile{
|
||||
URL: path,
|
||||
Path: path,
|
||||
Raw: raw,
|
||||
}
|
||||
for _, resolverConf := range wasmResolverConfigs {
|
||||
if resolverConf.Module == path {
|
||||
ref, err := ast.PtrRef(ast.DefaultRootDocument, resolverConf.Entrypoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse wasm module entrypoint '%s': %s", resolverConf.Entrypoint, err)
|
||||
}
|
||||
wmf.Entrypoints = append(wmf.Entrypoints, ref)
|
||||
}
|
||||
}
|
||||
if len(wmf.Entrypoints) > 0 {
|
||||
resolversToLoad = append(resolversToLoad, wmf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var resolvers []*wasm.Resolver
|
||||
if len(resolversToLoad) > 0 {
|
||||
// Get a full snapshot of the current data (including any from "outside" the bundles)
|
||||
data, err := store.Read(ctx, txn, storage.RootPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize wasm runtime: %s", err)
|
||||
}
|
||||
|
||||
for _, wmf := range resolversToLoad {
|
||||
resolver, err := wasm.New(wmf.Entrypoints, wmf.Raw, data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize wasm module for entrypoints '%s': %s", wmf.Entrypoints, err)
|
||||
}
|
||||
resolvers = append(resolvers, resolver)
|
||||
}
|
||||
}
|
||||
return resolvers, nil
|
||||
}
|
||||
|
||||
// LoadBundleFromDisk loads a previously persisted activated bundle from disk
|
||||
func LoadBundleFromDisk(path, name string, bvc *bundle.VerificationConfig) (*bundle.Bundle, error) {
|
||||
return LoadBundleFromDiskForRegoVersion(ast.RegoV0, path, name, bvc)
|
||||
}
|
||||
|
||||
func LoadBundleFromDiskForRegoVersion(regoVersion ast.RegoVersion, path, name string, bvc *bundle.VerificationConfig) (*bundle.Bundle, error) {
|
||||
bundlePath := filepath.Join(path, name, "bundle.tar.gz")
|
||||
|
||||
_, err := os.Stat(bundlePath)
|
||||
if err == nil {
|
||||
f, err := os.Open(bundlePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
r := bundle.NewCustomReader(bundle.NewTarballLoaderWithBaseURL(f, "")).
|
||||
WithRegoVersion(regoVersion)
|
||||
|
||||
if bvc != nil {
|
||||
r = r.WithBundleVerificationConfig(bvc)
|
||||
}
|
||||
|
||||
b, err := r.Read()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &b, nil
|
||||
} else if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// SaveBundleToDisk saves the given raw bytes representing the bundle's content to disk
|
||||
func SaveBundleToDisk(path string, raw io.Reader) (string, error) {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if raw == nil {
|
||||
return "", errors.New("no raw bundle bytes to persist to disk")
|
||||
}
|
||||
|
||||
dest, err := os.CreateTemp(path, ".bundle.tar.gz.*.tmp")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer dest.Close()
|
||||
|
||||
_, err = io.Copy(dest, raw)
|
||||
return dest.Name(), err
|
||||
}
|
||||
+367
@@ -0,0 +1,367 @@
|
||||
// Copyright 2017-2020 Authors of Cilium
|
||||
//
|
||||
// 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 merge provides helper functions for merging a list of
|
||||
// IP addresses and subnets into the smallest possible list of CIDRs.
|
||||
// Original Implementation: https://github.com/cilium/cilium
|
||||
package merge
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"math/big"
|
||||
"net"
|
||||
)
|
||||
|
||||
const (
|
||||
ipv4BitLen = 8 * net.IPv4len
|
||||
ipv6BitLen = 8 * net.IPv6len
|
||||
)
|
||||
|
||||
var (
|
||||
v4Mappedv6Prefix = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff}
|
||||
defaultIPv4 = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0}
|
||||
defaultIPv6 = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
|
||||
upperIPv4 = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 255, 255, 255, 255}
|
||||
upperIPv6 = []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
|
||||
ipv4LeadingZeroes = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
|
||||
)
|
||||
|
||||
// RangeToCIDRs converts the range of IPs covered by firstIP and lastIP to
|
||||
// a list of CIDRs that contains all of the IPs covered by the range.
|
||||
func RangeToCIDRs(firstIP, lastIP net.IP) []*net.IPNet {
|
||||
// First, create a CIDR that spans both IPs.
|
||||
spanningCIDR := createSpanningCIDR(&firstIP, &lastIP)
|
||||
firstIPSpanning, lastIPSpanning := GetAddressRange(spanningCIDR)
|
||||
|
||||
cidrList := []*net.IPNet{}
|
||||
|
||||
// If the first IP of the spanning CIDR passes the lower bound (firstIP),
|
||||
// we need to split the spanning CIDR and only take the IPs that are
|
||||
// greater than the value which we split on, as we do not want the lesser
|
||||
// values since they are less than the lower-bound (firstIP).
|
||||
if bytes.Compare(firstIPSpanning, firstIP) < 0 {
|
||||
// Split on the previous IP of the first IP so that the right list of IPs
|
||||
// of the partition includes the firstIP.
|
||||
prevFirstRangeIP := GetPreviousIP(firstIP)
|
||||
var bitLen int
|
||||
if prevFirstRangeIP.To4() != nil {
|
||||
bitLen = ipv4BitLen
|
||||
} else {
|
||||
bitLen = ipv6BitLen
|
||||
}
|
||||
_, _, right := partitionCIDR(spanningCIDR, net.IPNet{IP: prevFirstRangeIP, Mask: net.CIDRMask(bitLen, bitLen)})
|
||||
|
||||
// Append all CIDRs but the first, as this CIDR includes the upper
|
||||
// bound of the spanning CIDR, which we still need to partition on.
|
||||
cidrList = append(cidrList, right...)
|
||||
spanningCIDR = *right[0]
|
||||
cidrList = cidrList[1:]
|
||||
}
|
||||
|
||||
// Conversely, if the last IP of the spanning CIDR passes the upper bound
|
||||
// (lastIP), we need to split the spanning CIDR and only take the IPs that
|
||||
// are greater than the value which we split on, as we do not want the greater
|
||||
// values since they are greater than the upper-bound (lastIP).
|
||||
if bytes.Compare(lastIPSpanning, lastIP) > 0 {
|
||||
// Split on the next IP of the last IP so that the left list of IPs
|
||||
// of the partition include the lastIP.
|
||||
nextFirstRangeIP := getNextIP(lastIP)
|
||||
var bitLen int
|
||||
if nextFirstRangeIP.To4() != nil {
|
||||
bitLen = ipv4BitLen
|
||||
} else {
|
||||
bitLen = ipv6BitLen
|
||||
}
|
||||
left, _, _ := partitionCIDR(spanningCIDR, net.IPNet{IP: nextFirstRangeIP, Mask: net.CIDRMask(bitLen, bitLen)})
|
||||
cidrList = append(cidrList, left...)
|
||||
} else {
|
||||
// Otherwise, there is no need to partition; just use add the spanning
|
||||
// CIDR to the list of networks.
|
||||
cidrList = append(cidrList, &spanningCIDR)
|
||||
}
|
||||
return cidrList
|
||||
}
|
||||
|
||||
// GetAddressRange returns the first and last addresses in the given CIDR range.
|
||||
func GetAddressRange(ipNet net.IPNet) (net.IP, net.IP) {
|
||||
firstIP := make(net.IP, len(ipNet.IP))
|
||||
lastIP := make(net.IP, len(ipNet.IP))
|
||||
|
||||
copy(firstIP, ipNet.IP)
|
||||
copy(lastIP, ipNet.IP)
|
||||
|
||||
firstIP = firstIP.Mask(ipNet.Mask)
|
||||
lastIP = lastIP.Mask(ipNet.Mask)
|
||||
|
||||
if firstIP.To4() != nil {
|
||||
firstIP = append(v4Mappedv6Prefix, firstIP...)
|
||||
lastIP = append(v4Mappedv6Prefix, lastIP...)
|
||||
}
|
||||
|
||||
lastIPMask := make(net.IPMask, len(ipNet.Mask))
|
||||
copy(lastIPMask, ipNet.Mask)
|
||||
for i := range lastIPMask {
|
||||
lastIPMask[len(lastIPMask)-i-1] = ^lastIPMask[len(lastIPMask)-i-1]
|
||||
lastIP[net.IPv6len-i-1] |= lastIPMask[len(lastIPMask)-i-1]
|
||||
}
|
||||
|
||||
return firstIP, lastIP
|
||||
}
|
||||
|
||||
// GetPreviousIP returns the previous IP from the given IP address.
|
||||
func GetPreviousIP(ip net.IP) net.IP {
|
||||
// Cannot go lower than zero!
|
||||
if ip.Equal(net.IP(defaultIPv4)) || ip.Equal(net.IP(defaultIPv6)) {
|
||||
return ip
|
||||
}
|
||||
|
||||
previousIP := make(net.IP, len(ip))
|
||||
copy(previousIP, ip)
|
||||
|
||||
var overflow bool
|
||||
var lowerByteBound int
|
||||
if ip.To4() != nil {
|
||||
lowerByteBound = net.IPv6len - net.IPv4len
|
||||
} else {
|
||||
lowerByteBound = 0
|
||||
}
|
||||
for i := len(ip) - 1; i >= lowerByteBound; i-- {
|
||||
if overflow || i == len(ip)-1 {
|
||||
previousIP[i]--
|
||||
}
|
||||
// Track if we have overflowed and thus need to continue subtracting.
|
||||
if ip[i] == 0 && previousIP[i] == 255 {
|
||||
overflow = true
|
||||
} else {
|
||||
overflow = false
|
||||
}
|
||||
}
|
||||
return previousIP
|
||||
}
|
||||
|
||||
// createSpanningCIDR returns a single IP network spanning the
|
||||
// the lower and upper bound IP addresses.
|
||||
func createSpanningCIDR(firstIP, lastIP *net.IP) net.IPNet {
|
||||
// Don't want to modify the values of the provided range, so make copies.
|
||||
lowest := *firstIP
|
||||
highest := *lastIP
|
||||
|
||||
var isIPv4 bool
|
||||
var spanningMaskSize, bitLen, byteLen int
|
||||
if lowest.To4() != nil {
|
||||
isIPv4 = true
|
||||
bitLen = ipv4BitLen
|
||||
byteLen = net.IPv4len
|
||||
} else {
|
||||
bitLen = ipv6BitLen
|
||||
byteLen = net.IPv6len
|
||||
}
|
||||
|
||||
if isIPv4 {
|
||||
spanningMaskSize = ipv4BitLen
|
||||
} else {
|
||||
spanningMaskSize = ipv6BitLen
|
||||
}
|
||||
|
||||
// Convert to big Int so we can easily do bitshifting on the IP addresses,
|
||||
// since golang only provides up to 64-bit unsigned integers.
|
||||
lowestBig := big.NewInt(0).SetBytes(lowest)
|
||||
highestBig := big.NewInt(0).SetBytes(highest)
|
||||
|
||||
// Starting from largest mask / smallest range possible, apply a mask one bit
|
||||
// larger in each iteration to the upper bound in the range until we have
|
||||
// masked enough to pass the lower bound in the range. This
|
||||
// gives us the size of the prefix for the spanning CIDR to return as
|
||||
// well as the IP for the CIDR prefix of the spanning CIDR.
|
||||
for spanningMaskSize > 0 && lowestBig.Cmp(highestBig) < 0 {
|
||||
spanningMaskSize--
|
||||
mask := big.NewInt(1)
|
||||
mask = mask.Lsh(mask, uint(bitLen-spanningMaskSize))
|
||||
mask = mask.Mul(mask, big.NewInt(-1))
|
||||
highestBig = highestBig.And(highestBig, mask)
|
||||
}
|
||||
|
||||
// If ipv4, need to append 0s because math.Big gets rid of preceding zeroes.
|
||||
if isIPv4 {
|
||||
highest = append(ipv4LeadingZeroes, highestBig.Bytes()...)
|
||||
} else {
|
||||
highest = highestBig.Bytes()
|
||||
}
|
||||
|
||||
// Int does not store leading zeroes.
|
||||
if len(highest) == 0 {
|
||||
highest = make([]byte, byteLen)
|
||||
}
|
||||
|
||||
newNet := net.IPNet{IP: highest, Mask: net.CIDRMask(spanningMaskSize, bitLen)}
|
||||
return newNet
|
||||
}
|
||||
|
||||
// partitionCIDR returns a list of IP Networks partitioned upon excludeCIDR.
|
||||
// The first list contains the networks to the left of the excludeCIDR in the
|
||||
// partition, the second is a list containing the excludeCIDR itself if it is
|
||||
// contained within the targetCIDR (nil otherwise), and the
|
||||
// third is a list containing the networks to the right of the excludeCIDR in
|
||||
// the partition.
|
||||
func partitionCIDR(targetCIDR net.IPNet, excludeCIDR net.IPNet) ([]*net.IPNet, []*net.IPNet, []*net.IPNet) {
|
||||
var targetIsIPv4 bool
|
||||
if targetCIDR.IP.To4() != nil {
|
||||
targetIsIPv4 = true
|
||||
}
|
||||
|
||||
targetFirstIP, targetLastIP := GetAddressRange(targetCIDR)
|
||||
excludeFirstIP, excludeLastIP := GetAddressRange(excludeCIDR)
|
||||
|
||||
targetMaskSize, _ := targetCIDR.Mask.Size()
|
||||
excludeMaskSize, _ := excludeCIDR.Mask.Size()
|
||||
|
||||
if bytes.Compare(excludeLastIP, targetFirstIP) < 0 {
|
||||
return nil, nil, []*net.IPNet{&targetCIDR}
|
||||
} else if bytes.Compare(targetLastIP, excludeFirstIP) < 0 {
|
||||
return []*net.IPNet{&targetCIDR}, nil, nil
|
||||
}
|
||||
|
||||
if targetMaskSize >= excludeMaskSize {
|
||||
return nil, []*net.IPNet{&targetCIDR}, nil
|
||||
}
|
||||
|
||||
left := []*net.IPNet{}
|
||||
right := []*net.IPNet{}
|
||||
|
||||
newPrefixLen := targetMaskSize + 1
|
||||
|
||||
targetFirstCopy := make(net.IP, len(targetFirstIP))
|
||||
copy(targetFirstCopy, targetFirstIP)
|
||||
|
||||
iLowerOld := make(net.IP, len(targetFirstCopy))
|
||||
copy(iLowerOld, targetFirstCopy)
|
||||
|
||||
// Since golang only supports up to unsigned 64-bit integers, and we need
|
||||
// to perform addition on addresses, use math/big library, which allows
|
||||
// for manipulation of large integers.
|
||||
|
||||
// Used to track the current lower and upper bounds of the ranges to compare
|
||||
// to excludeCIDR.
|
||||
iLower := big.NewInt(0)
|
||||
iUpper := big.NewInt(0)
|
||||
iLower = iLower.SetBytes(targetFirstCopy)
|
||||
|
||||
var bitLen int
|
||||
|
||||
if targetIsIPv4 {
|
||||
bitLen = ipv4BitLen
|
||||
} else {
|
||||
bitLen = ipv6BitLen
|
||||
}
|
||||
shiftAmount := (uint)(bitLen - newPrefixLen)
|
||||
|
||||
targetIPInt := big.NewInt(0)
|
||||
targetIPInt.SetBytes(targetFirstIP.To16())
|
||||
|
||||
exp := big.NewInt(0)
|
||||
|
||||
// Use left shift for exponentiation
|
||||
exp = exp.Lsh(big.NewInt(1), shiftAmount)
|
||||
iUpper = iUpper.Add(targetIPInt, exp)
|
||||
|
||||
matched := big.NewInt(0)
|
||||
|
||||
for excludeMaskSize >= newPrefixLen {
|
||||
// Append leading zeros to IPv4 addresses, as math.Big.Int does not
|
||||
// append them when the IP address is copied from a byte array to
|
||||
// math.Big.Int. Leading zeroes are required for parsing IPv4 addresses
|
||||
// for use with net.IP / net.IPNet.
|
||||
var iUpperBytes, iLowerBytes []byte
|
||||
if targetIsIPv4 {
|
||||
iUpperBytes = append(ipv4LeadingZeroes, iUpper.Bytes()...)
|
||||
iLowerBytes = append(ipv4LeadingZeroes, iLower.Bytes()...)
|
||||
} else {
|
||||
iUpperBytesLen := len(iUpper.Bytes())
|
||||
// Make sure that the number of bytes in the array matches what net
|
||||
// package expects, as big package doesn't append leading zeroes.
|
||||
if iUpperBytesLen != net.IPv6len {
|
||||
numZeroesToAppend := net.IPv6len - iUpperBytesLen
|
||||
zeroBytes := make([]byte, numZeroesToAppend)
|
||||
iUpperBytes = append(zeroBytes, iUpper.Bytes()...)
|
||||
} else {
|
||||
iUpperBytes = iUpper.Bytes()
|
||||
|
||||
}
|
||||
|
||||
iLowerBytesLen := len(iLower.Bytes())
|
||||
if iLowerBytesLen != net.IPv6len {
|
||||
numZeroesToAppend := net.IPv6len - iLowerBytesLen
|
||||
zeroBytes := make([]byte, numZeroesToAppend)
|
||||
iLowerBytes = append(zeroBytes, iLower.Bytes()...)
|
||||
} else {
|
||||
iLowerBytes = iLower.Bytes()
|
||||
|
||||
}
|
||||
}
|
||||
// If the IP we are excluding over is of a higher value than the current
|
||||
// CIDR prefix we are generating, add the CIDR prefix to the set of IPs
|
||||
// to the left of the exclude CIDR
|
||||
if bytes.Compare(excludeFirstIP, iUpperBytes) >= 0 {
|
||||
left = append(left, &net.IPNet{IP: iLowerBytes, Mask: net.CIDRMask(newPrefixLen, bitLen)})
|
||||
matched = matched.Set(iUpper)
|
||||
} else {
|
||||
// Same as above, but opposite.
|
||||
right = append(right, &net.IPNet{IP: iUpperBytes, Mask: net.CIDRMask(newPrefixLen, bitLen)})
|
||||
matched = matched.Set(iLower)
|
||||
}
|
||||
|
||||
newPrefixLen++
|
||||
|
||||
if newPrefixLen > bitLen {
|
||||
break
|
||||
}
|
||||
|
||||
iLower = iLower.Set(matched)
|
||||
iUpper = iUpper.Add(matched, big.NewInt(0).Lsh(big.NewInt(1), uint(bitLen-newPrefixLen)))
|
||||
|
||||
}
|
||||
excludeList := []*net.IPNet{&excludeCIDR}
|
||||
|
||||
return left, excludeList, right
|
||||
}
|
||||
|
||||
func getNextIP(ip net.IP) net.IP {
|
||||
if ip.Equal(upperIPv4) || ip.Equal(upperIPv6) {
|
||||
return ip
|
||||
}
|
||||
|
||||
nextIP := make(net.IP, len(ip))
|
||||
switch len(ip) {
|
||||
case net.IPv4len:
|
||||
ipU32 := binary.BigEndian.Uint32(ip)
|
||||
ipU32++
|
||||
binary.BigEndian.PutUint32(nextIP, ipU32)
|
||||
return nextIP
|
||||
case net.IPv6len:
|
||||
ipU64 := binary.BigEndian.Uint64(ip[net.IPv6len/2:])
|
||||
ipU64++
|
||||
binary.BigEndian.PutUint64(nextIP[net.IPv6len/2:], ipU64)
|
||||
if ipU64 == 0 {
|
||||
ipU64 = binary.BigEndian.Uint64(ip[:net.IPv6len/2])
|
||||
ipU64++
|
||||
binary.BigEndian.PutUint64(nextIP[:net.IPv6len/2], ipU64)
|
||||
} else {
|
||||
copy(nextIP[:net.IPv6len/2], ip[:net.IPv6len/2])
|
||||
}
|
||||
return nextIP
|
||||
default:
|
||||
return ip
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
// 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 compiler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/ast"
|
||||
"github.com/open-policy-agent/opa/v1/schemas"
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
type SchemaFile string
|
||||
|
||||
const (
|
||||
AuthorizationPolicySchema SchemaFile = "authorizationPolicy.json"
|
||||
)
|
||||
|
||||
var schemaDefinitions = map[SchemaFile]any{}
|
||||
|
||||
var loadOnce = sync.OnceValue(func() error {
|
||||
cont, err := schemas.FS.ReadFile(string(AuthorizationPolicySchema))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(cont) == 0 {
|
||||
return errors.New("expected authorization policy schema file to be present")
|
||||
}
|
||||
|
||||
var schema any
|
||||
if err := util.Unmarshal(cont, &schema); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
schemaDefinitions[AuthorizationPolicySchema] = schema
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// VerifyAuthorizationPolicySchema performs type checking on rules against the schema for the Authorization Policy
|
||||
// Input document.
|
||||
// NOTE: The provided compiler should have already run the compilation process on the input modules
|
||||
func VerifyAuthorizationPolicySchema(compiler *ast.Compiler, ref ast.Ref) error {
|
||||
if err := loadOnce(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
rules := getRulesWithDependencies(compiler, ref)
|
||||
|
||||
if len(rules) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
schemaSet := ast.NewSchemaSet()
|
||||
schemaSet.Put(ast.SchemaRootRef, schemaDefinitions[AuthorizationPolicySchema])
|
||||
|
||||
errs := ast.NewCompiler().
|
||||
WithDefaultRegoVersion(compiler.DefaultRegoVersion()).
|
||||
WithSchemas(schemaSet).
|
||||
PassesTypeCheckRules(rules)
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getRulesWithDependencies returns a slice of rules that are referred to by ref along with their dependencies
|
||||
func getRulesWithDependencies(compiler *ast.Compiler, ref ast.Ref) []*ast.Rule {
|
||||
allRules := compiler.GetRules(ref)
|
||||
|
||||
deps := map[*ast.Rule]struct{}{}
|
||||
for _, rule := range allRules {
|
||||
transitiveDependencies(compiler, rule, deps)
|
||||
}
|
||||
|
||||
for dep := range deps {
|
||||
allRules = append(allRules, dep)
|
||||
}
|
||||
|
||||
return allRules
|
||||
}
|
||||
|
||||
func transitiveDependencies(compiler *ast.Compiler, rule *ast.Rule, deps map[*ast.Rule]struct{}) {
|
||||
for x := range compiler.Graph.Dependencies(rule) {
|
||||
other := x.(*ast.Rule)
|
||||
deps[other] = struct{}{}
|
||||
transitiveDependencies(compiler, other, deps)
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+2546
File diff suppressed because one or more lines are too long
+27
@@ -0,0 +1,27 @@
|
||||
// Copyright 2021 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 opa contains bytecode for the OPA-WASM library.
|
||||
package opa
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed opa.wasm
|
||||
var wasmBase []byte
|
||||
|
||||
//go:embed callgraph.csv
|
||||
var callGraphCSV []byte
|
||||
|
||||
// Bytes returns the OPA-WASM bytecode.
|
||||
func Bytes() []byte {
|
||||
return wasmBase
|
||||
}
|
||||
|
||||
// CallGraphCSV returns a CSV representation of the
|
||||
// OPA-WASM bytecode's call graph: 'caller,callee'
|
||||
func CallGraphCSV() []byte {
|
||||
return callGraphCSV
|
||||
}
|
||||
Generated
Vendored
BIN
Binary file not shown.
Generated
Vendored
+271
@@ -0,0 +1,271 @@
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/open-policy-agent/opa/internal/compiler/wasm/opa"
|
||||
"github.com/open-policy-agent/opa/internal/wasm/encoding"
|
||||
"github.com/open-policy-agent/opa/internal/wasm/instruction"
|
||||
"github.com/open-policy-agent/opa/internal/wasm/module"
|
||||
)
|
||||
|
||||
const warning = `---------------------------------------------------------------
|
||||
WARNING: Using EXPERIMENTAL, unsupported wasm-opt optimization.
|
||||
It is not supported, and may go away in the future.
|
||||
---------------------------------------------------------------`
|
||||
|
||||
// optimizeBinaryen passes the encoded module into wasm-opt, and replaces
|
||||
// the compiler's module with the decoding of the process' output.
|
||||
func (c *Compiler) optimizeBinaryen() error {
|
||||
if os.Getenv("EXPERIMENTAL_WASM_OPT") == "" && os.Getenv("EXPERIMENTAL_WASM_OPT_ARGS") == "" {
|
||||
c.debug.Printf("not opted in, skipping wasm-opt optimization")
|
||||
return nil
|
||||
}
|
||||
if !woptFound() {
|
||||
c.debug.Printf("wasm-opt binary not found, skipping optimization")
|
||||
return nil
|
||||
}
|
||||
if os.Getenv("EXPERIMENTAL_WASM_OPT") != "silent" { // for benchmarks
|
||||
fmt.Fprintln(os.Stderr, warning)
|
||||
}
|
||||
args := []string{ // WARNING: flags with typos are ignored!
|
||||
"-O2",
|
||||
"--debuginfo", // don't strip name section
|
||||
}
|
||||
// allow overriding the options
|
||||
if env := os.Getenv("EXPERIMENTAL_WASM_OPT_ARGS"); env != "" {
|
||||
args = strings.Split(env, " ")
|
||||
}
|
||||
|
||||
args = append(args, "-o", "-") // always output to stdout
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
wopt := exec.CommandContext(ctx, "wasm-opt", args...)
|
||||
stdin, err := wopt.StdinPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get stdin: %w", err)
|
||||
}
|
||||
defer stdin.Close()
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
wopt.Stdout = &stdout
|
||||
|
||||
if err := wopt.Start(); err != nil {
|
||||
return fmt.Errorf("start wasm-opt: %w", err)
|
||||
}
|
||||
if err := encoding.WriteModule(stdin, c.module); err != nil {
|
||||
return fmt.Errorf("encode module: %w", err)
|
||||
}
|
||||
if err := stdin.Close(); err != nil {
|
||||
return fmt.Errorf("write to wasm-opt: %w", err)
|
||||
}
|
||||
if err := wopt.Wait(); err != nil {
|
||||
return fmt.Errorf("wait for wasm-opt: %w", err)
|
||||
}
|
||||
|
||||
if d := stderr.String(); d != "" {
|
||||
c.debug.Printf("wasm-opt debug output: %s", d)
|
||||
}
|
||||
mod, err := encoding.ReadModule(&stdout)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode module: %w", err)
|
||||
}
|
||||
c.module = mod
|
||||
return nil
|
||||
}
|
||||
|
||||
func woptFound() bool {
|
||||
_, err := exec.LookPath("wasm-opt")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// NOTE(sr): Yes, there are more control instructions than these two,
|
||||
// but we haven't made use of them yet. So this function only checks
|
||||
// for the control instructions we're possibly emitting, and which are
|
||||
// relevant for block nesting.
|
||||
func withControlInstr(is []instruction.Instruction) bool {
|
||||
for _, i := range is {
|
||||
switch i := i.(type) {
|
||||
case instruction.Br, instruction.BrIf:
|
||||
return true
|
||||
case instruction.StructuredInstruction:
|
||||
// NOTE(sr): We could attempt to further flatten the nested blocks
|
||||
// here, but I believe we'd then have to correct block labels.
|
||||
if withControlInstr(i.Instructions()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func unquote(s string) (string, error) {
|
||||
return strconv.Unquote("\"" + strings.ReplaceAll(s, `\`, `\x`) + "\"")
|
||||
}
|
||||
|
||||
func (c *Compiler) removeUnusedCode() error {
|
||||
cgCSV := opa.CallGraphCSV()
|
||||
r := csv.NewReader(bytes.NewReader(cgCSV))
|
||||
r.LazyQuotes = true
|
||||
cg, err := r.ReadAll()
|
||||
if err != nil {
|
||||
return fmt.Errorf("csv read: %w", err)
|
||||
}
|
||||
|
||||
cgIdx := map[uint32][]uint32{}
|
||||
for i := range cg {
|
||||
callerName, err := unquote(cg[i][0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unquote caller name %s: %w", cg[i][0], err)
|
||||
}
|
||||
calleeName, err := unquote(cg[i][1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unquote callee name %s: %w", cg[i][1], err)
|
||||
}
|
||||
caller, ok := c.funcs[callerName]
|
||||
if !ok {
|
||||
continue // without a caller, it should get removed anyways (right?)
|
||||
}
|
||||
callee, ok := c.funcs[calleeName]
|
||||
if !ok {
|
||||
return fmt.Errorf("callee not found: %s (%s)", cg[i][1], calleeName)
|
||||
}
|
||||
cgIdx[caller] = append(cgIdx[caller], callee)
|
||||
}
|
||||
|
||||
// add the calls from planned functions
|
||||
for _, f := range c.funcsCode {
|
||||
fidx := c.funcs[f.name]
|
||||
cgIdx[fidx] = findCallees(f.code.Func.Expr.Instrs)
|
||||
}
|
||||
|
||||
keepFuncs := map[uint32]struct{}{}
|
||||
|
||||
// we'll keep
|
||||
// - what's referenced in a table (these could be called indirectly)
|
||||
// - what's exported or imported
|
||||
// - what's been compiled by us
|
||||
// - anything transitively called from those
|
||||
|
||||
for _, imp := range c.module.Import.Imports {
|
||||
if _, ok := imp.Descriptor.(module.FunctionImport); ok {
|
||||
reach(cgIdx, keepFuncs, c.funcs[imp.Name])
|
||||
}
|
||||
}
|
||||
|
||||
for _, exp := range c.module.Export.Exports {
|
||||
if exp.Descriptor.Type == module.FunctionExportType {
|
||||
reach(cgIdx, keepFuncs, c.funcs[exp.Name])
|
||||
}
|
||||
}
|
||||
|
||||
for _, f := range c.funcsCode {
|
||||
reach(cgIdx, keepFuncs, c.funcs[f.name])
|
||||
}
|
||||
|
||||
// anything referenced in a table
|
||||
for _, seg := range c.module.Element.Segments {
|
||||
for _, idx := range seg.Indices {
|
||||
if c.skipElemRE2(keepFuncs, idx) {
|
||||
c.debug.Printf("dropping element %d because policy does not depend on re2", idx)
|
||||
} else {
|
||||
reach(cgIdx, keepFuncs, idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove all that's not needed, update index for remaining ones
|
||||
funcNames := []module.NameMap{}
|
||||
for _, nm := range c.module.Names.Functions {
|
||||
if _, ok := keepFuncs[nm.Index]; ok {
|
||||
funcNames = append(funcNames, nm)
|
||||
}
|
||||
}
|
||||
c.module.Names.Functions = funcNames
|
||||
|
||||
// For anything that we don't want, replace the function code entries'
|
||||
// expressions with `unreachable`.
|
||||
// We do this because it lets the resulting wasm module pass `wasm-validate`,
|
||||
// empty bodies would not.
|
||||
nopEntry := module.Function{
|
||||
Expr: module.Expr{
|
||||
Instrs: []instruction.Instruction{instruction.Unreachable{}},
|
||||
},
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := encoding.WriteCodeEntry(&buf, &module.CodeEntry{Func: nopEntry}); err != nil {
|
||||
return fmt.Errorf("write code entry: %w", err)
|
||||
}
|
||||
for i := range c.module.Code.Segments {
|
||||
idx := i + c.functionImportCount()
|
||||
if _, ok := keepFuncs[uint32(idx)]; !ok {
|
||||
c.module.Code.Segments[i].Code = buf.Bytes()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findCallees(instrs []instruction.Instruction) []uint32 {
|
||||
var ret []uint32
|
||||
for _, expr := range instrs {
|
||||
switch expr := expr.(type) {
|
||||
case instruction.Call:
|
||||
ret = append(ret, expr.Index)
|
||||
case instruction.StructuredInstruction:
|
||||
ret = append(ret, findCallees(expr.Instructions())...)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func reach(cg map[uint32][]uint32, keep map[uint32]struct{}, node uint32) {
|
||||
if _, ok := keep[node]; !ok {
|
||||
keep[node] = struct{}{}
|
||||
for _, v := range cg[node] {
|
||||
reach(cg, keep, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// skipElemRE2 determines if a function in the table is really required:
|
||||
// We'll exclude anything with a prefix of "re2::" if none of the known
|
||||
// entrypoints into re2 are used.
|
||||
func (c *Compiler) skipElemRE2(keep map[uint32]struct{}, idx uint32) bool {
|
||||
if c.usesRE2(keep) {
|
||||
return false
|
||||
}
|
||||
return c.nameContains(idx, "re2::", "lexer::", "std::", "__cxa_pure_virtual", "operator", "parser_")
|
||||
}
|
||||
|
||||
func (c *Compiler) usesRE2(keep map[uint32]struct{}) bool {
|
||||
for _, fn := range builtinsUsingRE2 {
|
||||
if _, ok := keep[c.function(fn)]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *Compiler) nameContains(idx uint32, hs ...string) bool {
|
||||
// TODO(sr): keep reverse mapping (idx -> name) in Compiler struct
|
||||
for _, nm := range c.module.Names.Functions {
|
||||
if nm.Index == idx {
|
||||
for _, h := range hs {
|
||||
if strings.Contains(nm.Name, h) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
+1830
File diff suppressed because it is too large
Load Diff
+35
@@ -0,0 +1,35 @@
|
||||
package debug
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
)
|
||||
|
||||
// Debug allows printing debug messages.
|
||||
type Debug interface {
|
||||
// Printf prints, with a short file:line-number prefix
|
||||
Printf(format string, args ...any)
|
||||
// Writer returns the writer being written to, which may be
|
||||
// `io.Discard` if no debug output is requested.
|
||||
Writer() io.Writer
|
||||
|
||||
// Output allows tweaking the calldepth used for figuring
|
||||
// out which Go source file location is the interesting one,
|
||||
// i.e., which is included in the debug message. Useful for
|
||||
// setting up local helper methods.
|
||||
Output(calldepth int, s string) error
|
||||
}
|
||||
|
||||
// New returns a new `Debug` outputting to the passed `sink`.
|
||||
func New(sink io.Writer) Debug {
|
||||
flags := log.Lshortfile
|
||||
return log.New(sink, "", flags)
|
||||
}
|
||||
|
||||
// Discard returns a new `Debug` that doesn't output anything.
|
||||
// Note: We're not implementing the methods here with noop stubs
|
||||
// since doing this way, we can propagate the "discarding" via
|
||||
// `(Debug).Writer()`.
|
||||
func Discard() Debug {
|
||||
return New(io.Discard)
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
// 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 deepcopy
|
||||
|
||||
// DeepCopy performs a recursive deep copy for nested slices/maps and
|
||||
// returns the copied object. Supports []any
|
||||
// and map[string]any only
|
||||
func DeepCopy(val any) any {
|
||||
switch val := val.(type) {
|
||||
case []any:
|
||||
cpy := make([]any, len(val))
|
||||
for i := range cpy {
|
||||
cpy[i] = DeepCopy(val[i])
|
||||
}
|
||||
return cpy
|
||||
case map[string]any:
|
||||
return Map(val)
|
||||
default:
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
func Map(val map[string]any) map[string]any {
|
||||
cpy := make(map[string]any, len(val))
|
||||
for k := range val {
|
||||
cpy[k] = DeepCopy(val[k])
|
||||
}
|
||||
return cpy
|
||||
}
|
||||
Generated
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
# godropbox [](https://godoc.org/github.com/dropbox/godropbox) [](https://github.com/dropbox/godropbox/actions) [](https://github.com/dropbox/godropbox/actions)
|
||||
|
||||
Common libraries for writing go services/applications on Linux servers.
|
||||
|
||||
### Requirements
|
||||
* Go 1.13+
|
||||
* Linux/x64
|
||||
|
||||
### Installation
|
||||
``go get github.com/dropbox/godropbox``
|
||||
|
||||
### Documentation
|
||||
|
||||
See https://pkg.go.dev/github.com/dropbox/godropbox for modules documentation.
|
||||
|
||||
Generated
Vendored
+223
@@ -0,0 +1,223 @@
|
||||
// Package bitvector provides the implementation of a variable sized compact vector of bits
|
||||
// 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.
|
||||
//
|
||||
// Operations are not thread safe.
|
||||
type BitVector struct {
|
||||
data []byte
|
||||
length int
|
||||
}
|
||||
|
||||
// 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}
|
||||
}
|
||||
|
||||
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,
|
||||
// the contents of the bit vector may change.
|
||||
func (vector *BitVector) Bytes() []byte {
|
||||
return vector.data
|
||||
}
|
||||
|
||||
// Length returns the current number of elements in the bit vector.
|
||||
func (vector *BitVector) Length() int {
|
||||
return vector.length
|
||||
}
|
||||
|
||||
// This function shifts a byte slice one bit lower (less significant).
|
||||
// bit (either 1 or 0) contains the bit to put in the most significant
|
||||
// position of the last byte in the slice.
|
||||
// This returns the bit that was shifted off of the last byte.
|
||||
func shiftLower(bit byte, b []byte) byte {
|
||||
bit <<= 7
|
||||
for i := len(b) - 1; i >= 0; i-- {
|
||||
newByte := b[i] >> 1
|
||||
newByte |= bit
|
||||
bit = (b[i] & 1) << 7
|
||||
b[i] = newByte
|
||||
}
|
||||
return bit >> 7
|
||||
}
|
||||
|
||||
// This function shifts a byte slice one bit higher (more significant).
|
||||
// bit (either 1 or 0) contains the bit to put in the least significant
|
||||
// position of the first byte in the slice.
|
||||
// This returns the bit that was shifted off the last byte.
|
||||
func shiftHigher(bit byte, b []byte) byte {
|
||||
for i := range b {
|
||||
newByte := b[i] << 1
|
||||
newByte |= bit
|
||||
bit = (b[i] & 0x80) >> 7
|
||||
b[i] = newByte
|
||||
}
|
||||
return bit
|
||||
}
|
||||
|
||||
// Returns the minimum number of bytes needed for storing the bit vector.
|
||||
func (vector *BitVector) bytesLength() int {
|
||||
lastBitIndex := vector.length - 1
|
||||
lastByteIndex := lastBitIndex >> 3
|
||||
return lastByteIndex + 1
|
||||
}
|
||||
|
||||
// Panics if the given index is not within the bounds of the bit vector.
|
||||
func (vector *BitVector) indexAssert(i int) {
|
||||
if i < 0 || i >= vector.length {
|
||||
panic("Attempted to access element outside buffer")
|
||||
}
|
||||
}
|
||||
|
||||
// Append adds a bit to the end of a bit vector.
|
||||
func (vector *BitVector) Append(bit byte) {
|
||||
index := uint32(vector.length)
|
||||
vector.length++
|
||||
|
||||
if vector.bytesLength() > len(vector.data) {
|
||||
vector.data = append(vector.data, 0)
|
||||
}
|
||||
|
||||
byteIndex := index >> 3
|
||||
byteOffset := index % 8
|
||||
oldByte := vector.data[byteIndex]
|
||||
var newByte byte
|
||||
if bit == 1 {
|
||||
newByte = oldByte | 1<<byteOffset
|
||||
} else {
|
||||
// Set all bits except the byteOffset
|
||||
mask := byte(^(1 << byteOffset))
|
||||
newByte = oldByte & mask
|
||||
}
|
||||
|
||||
vector.data[byteIndex] = newByte
|
||||
}
|
||||
|
||||
// Element returns the bit in the ith index of the bit vector.
|
||||
// Returned value is either 1 or 0.
|
||||
func (vector *BitVector) Element(i int) byte {
|
||||
vector.indexAssert(i)
|
||||
byteIndex := i >> 3
|
||||
byteOffset := uint32(i % 8)
|
||||
b := vector.data[byteIndex]
|
||||
// Check the offset bit
|
||||
return (b >> byteOffset) & 1
|
||||
}
|
||||
|
||||
// Set changes the bit in the ith index of the bit vector to the value specified in
|
||||
// bit.
|
||||
func (vector *BitVector) Set(bit byte, index int) {
|
||||
vector.indexAssert(index)
|
||||
byteIndex := uint32(index >> 3)
|
||||
byteOffset := uint32(index % 8)
|
||||
|
||||
oldByte := vector.data[byteIndex]
|
||||
|
||||
var newByte byte
|
||||
if bit == 1 {
|
||||
// turn on the byteOffset'th bit
|
||||
newByte = oldByte | 1<<byteOffset
|
||||
} else {
|
||||
// turn off the byteOffset'th bit
|
||||
removeMask := byte(^(1 << byteOffset))
|
||||
newByte = oldByte & removeMask
|
||||
}
|
||||
vector.data[byteIndex] = newByte
|
||||
}
|
||||
|
||||
// Insert inserts bit into the supplied index of the bit vector. All
|
||||
// bits in positions greater than or equal to index before the call will
|
||||
// be shifted up by one.
|
||||
func (vector *BitVector) Insert(bit byte, index int) {
|
||||
vector.indexAssert(index)
|
||||
vector.length++
|
||||
|
||||
// Append an additional byte if necessary.
|
||||
if vector.bytesLength() > len(vector.data) {
|
||||
vector.data = append(vector.data, 0)
|
||||
}
|
||||
|
||||
byteIndex := uint32(index >> 3)
|
||||
byteOffset := uint32(index % 8)
|
||||
var bitToInsert byte
|
||||
if bit == 1 {
|
||||
bitToInsert = 1 << byteOffset
|
||||
}
|
||||
|
||||
oldByte := vector.data[byteIndex]
|
||||
// This bit will need to be shifted into the next byte
|
||||
leftoverBit := (oldByte & 0x80) >> 7
|
||||
// Make masks to pull off the bits below and above byteOffset
|
||||
// This mask has the byteOffset lowest bits set.
|
||||
bottomMask := byte((1 << byteOffset) - 1)
|
||||
// This mask has the 8 - byteOffset top bits set.
|
||||
topMask := ^bottomMask
|
||||
top := (oldByte & topMask) << 1
|
||||
newByte := bitToInsert | (oldByte & bottomMask) | top
|
||||
|
||||
vector.data[byteIndex] = newByte
|
||||
// Shift the rest of the bytes in the slice one higher, append
|
||||
// the leftoverBit obtained above.
|
||||
shiftHigher(leftoverBit, vector.data[byteIndex+1:])
|
||||
}
|
||||
|
||||
// Delete removes the bit in the supplied index of the bit vector. All
|
||||
// bits in positions greater than or equal to index before the call will
|
||||
// be shifted down by one.
|
||||
func (vector *BitVector) Delete(index int) {
|
||||
vector.indexAssert(index)
|
||||
vector.length--
|
||||
byteIndex := uint32(index >> 3)
|
||||
byteOffset := uint32(index % 8)
|
||||
|
||||
oldByte := vector.data[byteIndex]
|
||||
|
||||
// Shift all the bytes above the byte we're modifying, return the
|
||||
// leftover bit to include in the byte we're modifying.
|
||||
bit := shiftLower(0, vector.data[byteIndex+1:])
|
||||
|
||||
// Modify oldByte.
|
||||
// At a high level, we want to select the bits above byteOffset,
|
||||
// and shift them down by one, removing the bit at byteOffset.
|
||||
|
||||
// This selects the bottom bits
|
||||
bottomMask := byte((1 << byteOffset) - 1)
|
||||
// This selects the top (8 - byteOffset - 1) bits
|
||||
topMask := byte(^((1 << (byteOffset + 1)) - 1))
|
||||
// newTop is the top bits, shifted down one, combined with the leftover bit from shifting
|
||||
// the other bytes.
|
||||
newTop := (oldByte&topMask)>>1 | (bit << 7)
|
||||
// newByte takes the bottom bits and combines with the new top.
|
||||
newByte := (bottomMask & oldByte) | newTop
|
||||
vector.data[byteIndex] = newByte
|
||||
|
||||
// The desired length is the byte index of the last element plus one,
|
||||
// where the byte index of the last element is the bit index of the last
|
||||
// element divided by 8.
|
||||
byteLength := vector.bytesLength()
|
||||
if byteLength < len(vector.data) {
|
||||
vector.data = vector.data[:byteLength]
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2014 Dropbox, Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+1169
File diff suppressed because it is too large
Load Diff
+75
@@ -0,0 +1,75 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type TarGzWriter struct {
|
||||
*tar.Writer
|
||||
|
||||
gw *gzip.Writer
|
||||
}
|
||||
|
||||
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: path,
|
||||
Mode: 0600,
|
||||
Typeflag: tar.TypeReg,
|
||||
Size: int64(len(bs)),
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// Copyright 2019 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 url contains helpers for dealing with file paths and URLs.
|
||||
package url
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var goos = runtime.GOOS
|
||||
|
||||
// Clean returns a cleaned file path that may or may not be a URL.
|
||||
func Clean(path string) (string, error) {
|
||||
|
||||
if strings.Contains(path, "://") {
|
||||
|
||||
url, err := url.Parse(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if url.Scheme != "file" {
|
||||
return "", fmt.Errorf("unsupported URL scheme: %v", path)
|
||||
}
|
||||
|
||||
path = url.Path
|
||||
|
||||
// Trim leading slash on Windows if present. The url.Path field returned
|
||||
// by url.Parse has leading slash that causes CreateFile() calls to fail
|
||||
// on Windows. See https://github.com/golang/go/issues/6027 for details.
|
||||
if goos == "windows" && len(path) >= 1 && path[0] == '/' {
|
||||
path = path[1:]
|
||||
}
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
// Copyright 2021 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 future
|
||||
|
||||
import "github.com/open-policy-agent/opa/v1/ast"
|
||||
|
||||
// FilterFutureImports filters OUT any future imports from the passed slice of
|
||||
// `*ast.Import`s.
|
||||
func FilterFutureImports(imps []*ast.Import) []*ast.Import {
|
||||
ret := []*ast.Import{}
|
||||
for _, imp := range imps {
|
||||
path := imp.Path.Value.(ast.Ref)
|
||||
if !ast.FutureRootDocument.Equal(path[0]) {
|
||||
ret = append(ret, imp)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// IsAllFutureKeywords returns true if the passed *ast.Import is `future.keywords`
|
||||
func IsAllFutureKeywords(imp *ast.Import) bool {
|
||||
path := imp.Path.Value.(ast.Ref)
|
||||
return len(path) == 2 &&
|
||||
ast.FutureRootDocument.Equal(path[0]) &&
|
||||
path[1].Equal(ast.InternedTerm("keywords"))
|
||||
}
|
||||
|
||||
// IsFutureKeyword returns true if the passed *ast.Import is `future.keywords.{kw}`
|
||||
func IsFutureKeyword(imp *ast.Import, kw string) bool {
|
||||
path := imp.Path.Value.(ast.Ref)
|
||||
return len(path) == 3 &&
|
||||
ast.FutureRootDocument.Equal(path[0]) &&
|
||||
path[1].Equal(ast.InternedTerm("keywords")) &&
|
||||
path[2].Equal(ast.StringTerm(kw))
|
||||
}
|
||||
|
||||
func WhichFutureKeyword(imp *ast.Import) (string, bool) {
|
||||
path := imp.Path.Value.(ast.Ref)
|
||||
if len(path) == 3 &&
|
||||
ast.FutureRootDocument.Equal(path[0]) &&
|
||||
path[1].Equal(ast.InternedTerm("keywords")) {
|
||||
if str, ok := path[2].Value.(ast.String); ok {
|
||||
return string(str), true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// Copyright 2021 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 future
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/ast"
|
||||
)
|
||||
|
||||
// ParserOptionsFromFutureImports transforms a slice of `ast.Import`s into the
|
||||
// `ast.ParserOptions` that can be used to parse a statement according to the
|
||||
// included "future.keywords" and "future.keywords.xyz" imports.
|
||||
func ParserOptionsFromFutureImports(imports []*ast.Import) (ast.ParserOptions, error) {
|
||||
popts := ast.ParserOptions{
|
||||
FutureKeywords: []string{},
|
||||
}
|
||||
for _, imp := range imports {
|
||||
path := imp.Path.Value.(ast.Ref)
|
||||
if !ast.FutureRootDocument.Equal(path[0]) {
|
||||
continue
|
||||
}
|
||||
if len(path) >= 2 {
|
||||
if string(path[1].Value.(ast.String)) != "keywords" {
|
||||
return popts, fmt.Errorf("unknown future import: %v", imp)
|
||||
}
|
||||
if len(path) == 2 {
|
||||
// retun, one "future.keywords" import means we can disregard any others
|
||||
return ast.ParserOptions{AllFutureKeywords: true}, nil
|
||||
}
|
||||
}
|
||||
if len(path) == 3 {
|
||||
if imp.Alias != "" {
|
||||
return popts, errors.New("alias not supported")
|
||||
}
|
||||
popts.FutureKeywords = append(popts.FutureKeywords, string(path[2].Value.(ast.String)))
|
||||
}
|
||||
}
|
||||
return popts, nil
|
||||
}
|
||||
Generated
Vendored
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
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:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) 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
|
||||
|
||||
(d) 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 2015 xeipuuv
|
||||
|
||||
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.
|
||||
+482
@@ -0,0 +1,482 @@
|
||||
# gojsonschema (Details of library residing in OPA's internal)
|
||||
|
||||
## Description
|
||||
|
||||
https://github.com/xeipuuv/gojsonschema was duplicated into `internal/gojsonschema` folder and modified to make it possible
|
||||
to set Rego types in OPA, using schemas returned from `gojsonschema`s `Compile` utility.
|
||||
|
||||
The modifications done are as below:
|
||||
|
||||
1. Some of the private fields in `gojsonschema`'s structs, `schema` and `subSchema` have been exported (publicized) so that OPA's type checking code can access and manipulate the values returned by `gojsonschema`s `Compile` method,
|
||||
|
||||
2. Also, other changes in `gojsonschema`'s code include fixes to satisfy OPA's lint and format checker scripts. Hence, this `internal/gojsonschema` is in conformance with OPA's code style.
|
||||
|
||||
3. Modernize usage of Go in `gojsonschema`, using conventions like type switching and language-built-in map accessing rather than helper methods.
|
||||
|
||||
[](https://godoc.org/github.com/xeipuuv/gojsonschema)
|
||||
[](https://travis-ci.org/xeipuuv/gojsonschema)
|
||||
[](https://goreportcard.com/report/github.com/xeipuuv/gojsonschema)
|
||||
|
||||
# gojsonschema (Details of original parent repository from README)
|
||||
|
||||
## Description
|
||||
|
||||
An implementation of JSON Schema for the Go programming language. Supports draft-04, draft-06 and draft-07.
|
||||
|
||||
References :
|
||||
|
||||
* http://json-schema.org
|
||||
* http://json-schema.org/latest/json-schema-core.html
|
||||
* http://json-schema.org/latest/json-schema-validation.html
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
go get github.com/xeipuuv/gojsonschema
|
||||
```
|
||||
|
||||
Dependencies :
|
||||
* [github.com/xeipuuv/gojsonpointer](https://github.com/xeipuuv/gojsonpointer)
|
||||
* [github.com/xeipuuv/gojsonreference](https://github.com/xeipuuv/gojsonreference)
|
||||
* [github.com/stretchr/testify/assert](https://github.com/stretchr/testify#assert-package) - (Note, this dependency has
|
||||
been removed to reduce dependencies in OPA)
|
||||
|
||||
## Usage
|
||||
|
||||
### Example
|
||||
|
||||
```go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/xeipuuv/gojsonschema"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
schemaLoader := gojsonschema.NewReferenceLoader("file:///home/me/schema.json")
|
||||
documentLoader := gojsonschema.NewReferenceLoader("file:///home/me/document.json")
|
||||
|
||||
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
if result.Valid() {
|
||||
fmt.Printf("The document is valid\n")
|
||||
} else {
|
||||
fmt.Printf("The document is not valid. see errors :\n")
|
||||
for _, desc := range result.Errors() {
|
||||
fmt.Printf("- %s\n", desc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
#### Loaders
|
||||
|
||||
There are various ways to load your JSON data.
|
||||
In order to load your schemas and documents,
|
||||
first declare an appropriate loader :
|
||||
|
||||
* Web / HTTP, using a reference :
|
||||
|
||||
```go
|
||||
loader := gojsonschema.NewReferenceLoader("http://www.some_host.com/schema.json")
|
||||
```
|
||||
|
||||
* Local file, using a reference :
|
||||
|
||||
```go
|
||||
loader := gojsonschema.NewReferenceLoader("file:///home/me/schema.json")
|
||||
```
|
||||
|
||||
References use the URI scheme, the prefix (file://) and a full path to the file are required.
|
||||
|
||||
* JSON strings :
|
||||
|
||||
```go
|
||||
loader := gojsonschema.NewStringLoader(`{"type": "string"}`)
|
||||
```
|
||||
|
||||
* Custom Go types :
|
||||
|
||||
```go
|
||||
m := map[string]interface{}{"type": "string"}
|
||||
loader := gojsonschema.NewGoLoader(m)
|
||||
```
|
||||
|
||||
And
|
||||
|
||||
```go
|
||||
type Root struct {
|
||||
Users []User `json:"users"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
data := Root{}
|
||||
data.Users = append(data.Users, User{"John"})
|
||||
data.Users = append(data.Users, User{"Sophia"})
|
||||
data.Users = append(data.Users, User{"Bill"})
|
||||
|
||||
loader := gojsonschema.NewGoLoader(data)
|
||||
```
|
||||
|
||||
#### Validation
|
||||
|
||||
Once the loaders are set, validation is easy :
|
||||
|
||||
```go
|
||||
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
|
||||
```
|
||||
|
||||
Alternatively, you might want to load a schema only once and process to multiple validations :
|
||||
|
||||
```go
|
||||
schema, err := gojsonschema.NewSchema(schemaLoader)
|
||||
...
|
||||
result1, err := schema.Validate(documentLoader1)
|
||||
...
|
||||
result2, err := schema.Validate(documentLoader2)
|
||||
...
|
||||
// etc ...
|
||||
```
|
||||
|
||||
To check the result :
|
||||
|
||||
```go
|
||||
if result.Valid() {
|
||||
fmt.Printf("The document is valid\n")
|
||||
} else {
|
||||
fmt.Printf("The document is not valid. see errors :\n")
|
||||
for _, err := range result.Errors() {
|
||||
// Err implements the ResultError interface
|
||||
fmt.Printf("- %s\n", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Loading local schemas
|
||||
|
||||
By default `file` and `http(s)` references to external schemas are loaded automatically via the file system or via http(s). An external schema can also be loaded using a `SchemaLoader`.
|
||||
|
||||
```go
|
||||
sl := gojsonschema.NewSchemaLoader()
|
||||
loader1 := gojsonschema.NewStringLoader(`{ "type" : "string" }`)
|
||||
err := sl.AddSchema("http://some_host.com/string.json", loader1)
|
||||
```
|
||||
|
||||
Alternatively if your schema already has an `$id` you can use the `AddSchemas` function
|
||||
```go
|
||||
loader2 := gojsonschema.NewStringLoader(`{
|
||||
"$id" : "http://some_host.com/maxlength.json",
|
||||
"maxLength" : 5
|
||||
}`)
|
||||
err = sl.AddSchemas(loader2)
|
||||
```
|
||||
|
||||
The main schema should be passed to the `Compile` function. This main schema can then directly reference the added schemas without needing to download them.
|
||||
```go
|
||||
loader3 := gojsonschema.NewStringLoader(`{
|
||||
"$id" : "http://some_host.com/main.json",
|
||||
"allOf" : [
|
||||
{ "$ref" : "http://some_host.com/string.json" },
|
||||
{ "$ref" : "http://some_host.com/maxlength.json" }
|
||||
]
|
||||
}`)
|
||||
|
||||
schema, err := sl.Compile(loader3)
|
||||
|
||||
documentLoader := gojsonschema.NewStringLoader(`"hello world"`)
|
||||
|
||||
result, err := schema.Validate(documentLoader)
|
||||
```
|
||||
|
||||
It's also possible to pass a `ReferenceLoader` to the `Compile` function that references a loaded schema.
|
||||
|
||||
```go
|
||||
err = sl.AddSchemas(loader3)
|
||||
schema, err := sl.Compile(gojsonschema.NewReferenceLoader("http://some_host.com/main.json"))
|
||||
```
|
||||
|
||||
Schemas added by `AddSchema` and `AddSchemas` are only validated when the entire schema is compiled, unless meta-schema validation is used.
|
||||
|
||||
## Using a specific draft
|
||||
By default `gojsonschema` will try to detect the draft of a schema by using the `$schema` keyword and parse it in a strict draft-04, draft-06 or draft-07 mode. If `$schema` is missing, or the draft version is not explicitely set, a hybrid mode is used which merges together functionality of all drafts into one mode.
|
||||
|
||||
Autodectection can be turned off with the `AutoDetect` property. Specific draft versions can be specified with the `Draft` property.
|
||||
|
||||
```go
|
||||
sl := gojsonschema.NewSchemaLoader()
|
||||
sl.Draft = gojsonschema.Draft7
|
||||
sl.AutoDetect = false
|
||||
```
|
||||
|
||||
If autodetection is on (default), a draft-07 schema can savely reference draft-04 schemas and vice-versa, as long as `$schema` is specified in all schemas.
|
||||
|
||||
## Meta-schema validation
|
||||
Schemas that are added using the `AddSchema`, `AddSchemas` and `Compile` can be validated against their meta-schema by setting the `Validate` property.
|
||||
|
||||
The following example will produce an error as `multipleOf` must be a number. If `Validate` is off (default), this error is only returned at the `Compile` step.
|
||||
|
||||
```go
|
||||
sl := gojsonschema.NewSchemaLoader()
|
||||
sl.Validate = true
|
||||
err := sl.AddSchemas(gojsonschema.NewStringLoader(`{
|
||||
"$id" : "http://some_host.com/invalid.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"multipleOf" : true
|
||||
}`))
|
||||
```
|
||||
```
|
||||
```
|
||||
|
||||
Errors returned by meta-schema validation are more readable and contain more information, which helps significantly if you are developing a schema.
|
||||
|
||||
Meta-schema validation also works with a custom `$schema`. In case `$schema` is missing, or `AutoDetect` is set to `false`, the meta-schema of the used draft is used.
|
||||
|
||||
|
||||
## Working with Errors
|
||||
|
||||
The library handles string error codes which you can customize by creating your own gojsonschema.locale and setting it
|
||||
```go
|
||||
gojsonschema.Locale = YourCustomLocale{}
|
||||
```
|
||||
|
||||
However, each error contains additional contextual information.
|
||||
|
||||
Newer versions of `gojsonschema` may have new additional errors, so code that uses a custom locale will need to be updated when this happens.
|
||||
|
||||
**err.Type()**: *string* Returns the "type" of error that occurred. Note you can also type check. See below
|
||||
|
||||
Note: An error of RequiredType has an err.Type() return value of "required"
|
||||
|
||||
"required": RequiredError
|
||||
"invalid_type": InvalidTypeError
|
||||
"number_any_of": NumberAnyOfError
|
||||
"number_one_of": NumberOneOfError
|
||||
"number_all_of": NumberAllOfError
|
||||
"number_not": NumberNotError
|
||||
"missing_dependency": MissingDependencyError
|
||||
"internal": InternalError
|
||||
"const": ConstEror
|
||||
"enum": EnumError
|
||||
"array_no_additional_items": ArrayNoAdditionalItemsError
|
||||
"array_min_items": ArrayMinItemsError
|
||||
"array_max_items": ArrayMaxItemsError
|
||||
"unique": ItemsMustBeUniqueError
|
||||
"contains" : ArrayContainsError
|
||||
"array_min_properties": ArrayMinPropertiesError
|
||||
"array_max_properties": ArrayMaxPropertiesError
|
||||
"additional_property_not_allowed": AdditionalPropertyNotAllowedError
|
||||
"invalid_property_pattern": InvalidPropertyPatternError
|
||||
"invalid_property_name": InvalidPropertyNameError
|
||||
"string_gte": StringLengthGTEError
|
||||
"string_lte": StringLengthLTEError
|
||||
"pattern": DoesNotMatchPatternError
|
||||
"multiple_of": MultipleOfError
|
||||
"number_gte": NumberGTEError
|
||||
"number_gt": NumberGTError
|
||||
"number_lte": NumberLTEError
|
||||
"number_lt": NumberLTError
|
||||
"condition_then" : ConditionThenError
|
||||
"condition_else" : ConditionElseError
|
||||
|
||||
**err.Value()**: *interface{}* Returns the value given
|
||||
|
||||
**err.Context()**: *gojsonschema.JsonContext* Returns the context. This has a String() method that will print something like this: (root).firstName
|
||||
|
||||
**err.Field()**: *string* Returns the fieldname in the format firstName, or for embedded properties, person.firstName. This returns the same as the String() method on *err.Context()* but removes the (root). prefix.
|
||||
|
||||
**err.Description()**: *string* The error description. This is based on the locale you are using. See the beginning of this section for overwriting the locale with a custom implementation.
|
||||
|
||||
**err.DescriptionFormat()**: *string* The error description format. This is relevant if you are adding custom validation errors afterwards to the result.
|
||||
|
||||
**err.Details()**: *gojsonschema.ErrorDetails* Returns a map[string]interface{} of additional error details specific to the error. For example, GTE errors will have a "min" value, LTE will have a "max" value. See errors.go for a full description of all the error details. Every error always contains a "field" key that holds the value of *err.Field()*
|
||||
|
||||
Note in most cases, the err.Details() will be used to generate replacement strings in your locales, and not used directly. These strings follow the text/template format i.e.
|
||||
```
|
||||
{{.field}} must be greater than or equal to {{.min}}
|
||||
```
|
||||
|
||||
The library allows you to specify custom template functions, should you require more complex error message handling.
|
||||
```go
|
||||
gojsonschema.ErrorTemplateFuncs = map[string]interface{}{
|
||||
"allcaps": func(s string) string {
|
||||
return strings.ToUpper(s)
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Given the above definition, you can use the custom function `"allcaps"` in your localization templates:
|
||||
```
|
||||
{{allcaps .field}} must be greater than or equal to {{.min}}
|
||||
```
|
||||
|
||||
The above error message would then be rendered with the `field` value in capital letters. For example:
|
||||
```
|
||||
"PASSWORD must be greater than or equal to 8"
|
||||
```
|
||||
|
||||
Learn more about what types of template functions you can use in `ErrorTemplateFuncs` by referring to Go's [text/template FuncMap](https://golang.org/pkg/text/template/#FuncMap) type.
|
||||
|
||||
## Formats
|
||||
JSON Schema allows for optional "format" property to validate instances against well-known formats. gojsonschema ships with all of the formats defined in the spec that you can use like this:
|
||||
|
||||
````json
|
||||
{"type": "string", "format": "email"}
|
||||
````
|
||||
|
||||
Not all formats defined in draft-07 are available. Implemented formats are:
|
||||
|
||||
* `date`
|
||||
* `time`
|
||||
* `date-time`
|
||||
* `hostname`. Subdomains that start with a number are also supported, but this means that it doesn't strictly follow [RFC1034](http://tools.ietf.org/html/rfc1034#section-3.5) and has the implication that ipv4 addresses are also recognized as valid hostnames.
|
||||
* `email`. Go's email parser deviates slightly from [RFC5322](https://tools.ietf.org/html/rfc5322). Includes unicode support.
|
||||
* `idn-email`. Same caveat as `email`.
|
||||
* `ipv4`
|
||||
* `ipv6`
|
||||
* `uri`. Includes unicode support.
|
||||
* `uri-reference`. Includes unicode support.
|
||||
* `iri`
|
||||
* `iri-reference`
|
||||
* `uri-template`
|
||||
* `uuid`
|
||||
* `regex`. Go uses the [RE2](https://github.com/google/re2/wiki/Syntax) engine and is not [ECMA262](http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf) compatible.
|
||||
* `json-pointer`
|
||||
* `relative-json-pointer`
|
||||
|
||||
`email`, `uri` and `uri-reference` use the same validation code as their unicode counterparts `idn-email`, `iri` and `iri-reference`. If you rely on unicode support you should use the specific
|
||||
unicode enabled formats for the sake of interoperability as other implementations might not support unicode in the regular formats.
|
||||
|
||||
The validation code for `uri`, `idn-email` and their relatives use mostly standard library code.
|
||||
|
||||
For repetitive or more complex formats, you can create custom format checkers and add them to gojsonschema like this:
|
||||
|
||||
```go
|
||||
// Define the format checker
|
||||
type RoleFormatChecker struct {}
|
||||
|
||||
// Ensure it meets the gojsonschema.FormatChecker interface
|
||||
func (f RoleFormatChecker) IsFormat(input interface{}) bool {
|
||||
|
||||
asString, ok := input.(string)
|
||||
if ok == false {
|
||||
return false
|
||||
}
|
||||
|
||||
return strings.HasPrefix("ROLE_", asString)
|
||||
}
|
||||
|
||||
// Add it to the library
|
||||
gojsonschema.FormatCheckers.Add("role", RoleFormatChecker{})
|
||||
````
|
||||
|
||||
Now to use in your json schema:
|
||||
````json
|
||||
{"type": "string", "format": "role"}
|
||||
````
|
||||
|
||||
Another example would be to check if the provided integer matches an id on database:
|
||||
|
||||
JSON schema:
|
||||
```json
|
||||
{"type": "integer", "format": "ValidUserId"}
|
||||
```
|
||||
|
||||
```go
|
||||
// Define the format checker
|
||||
type ValidUserIdFormatChecker struct {}
|
||||
|
||||
// Ensure it meets the gojsonschema.FormatChecker interface
|
||||
func (f ValidUserIdFormatChecker) IsFormat(input interface{}) bool {
|
||||
|
||||
asFloat64, ok := input.(float64) // Numbers are always float64 here
|
||||
if ok == false {
|
||||
return false
|
||||
}
|
||||
|
||||
// XXX
|
||||
// do the magic on the database looking for the int(asFloat64)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Add it to the library
|
||||
gojsonschema.FormatCheckers.Add("ValidUserId", ValidUserIdFormatChecker{})
|
||||
````
|
||||
|
||||
Formats can also be removed, for example if you want to override one of the formats that is defined by default.
|
||||
|
||||
```go
|
||||
gojsonschema.FormatCheckers.Remove("hostname")
|
||||
```
|
||||
|
||||
|
||||
## Additional custom validation
|
||||
After the validation has run and you have the results, you may add additional
|
||||
errors using `Result.AddError`. This is useful to maintain the same format within the resultset instead
|
||||
of having to add special exceptions for your own errors. Below is an example.
|
||||
|
||||
```go
|
||||
type AnswerInvalidError struct {
|
||||
gojsonschema.ResultErrorFields
|
||||
}
|
||||
|
||||
func newAnswerInvalidError(context *gojsonschema.JsonContext, value interface{}, details gojsonschema.ErrorDetails) *AnswerInvalidError {
|
||||
err := AnswerInvalidError{}
|
||||
err.SetContext(context)
|
||||
err.SetType("custom_invalid_error")
|
||||
// it is important to use SetDescriptionFormat() as this is used to call SetDescription() after it has been parsed
|
||||
// using the description of err will be overridden by this.
|
||||
err.SetDescriptionFormat("Answer to the Ultimate Question of Life, the Universe, and Everything is {{.answer}}")
|
||||
err.SetValue(value)
|
||||
err.SetDetails(details)
|
||||
|
||||
return &err
|
||||
}
|
||||
|
||||
func main() {
|
||||
// ...
|
||||
schema, err := gojsonschema.NewSchema(schemaLoader)
|
||||
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
|
||||
|
||||
if true { // some validation
|
||||
jsonContext := gojsonschema.NewJsonContext("question", nil)
|
||||
errDetail := gojsonschema.ErrorDetails{
|
||||
"answer": 42,
|
||||
}
|
||||
result.AddError(
|
||||
newAnswerInvalidError(
|
||||
gojsonschema.NewJsonContext("answer", jsonContext),
|
||||
52,
|
||||
errDetail,
|
||||
),
|
||||
errDetail,
|
||||
)
|
||||
}
|
||||
|
||||
return result, err
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
This is especially useful if you want to add validation beyond what the
|
||||
json schema drafts can provide such business specific logic.
|
||||
|
||||
## Uses
|
||||
|
||||
gojsonschema uses the following test suite :
|
||||
|
||||
https://github.com/json-schema/JSON-Schema-Test-Suite
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
// Copyright 2018 johandorland ( https://github.com/johandorland )
|
||||
//
|
||||
// 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 gojsonschema
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
|
||||
"github.com/xeipuuv/gojsonreference"
|
||||
)
|
||||
|
||||
// Draft is a JSON-schema draft version
|
||||
type Draft int
|
||||
|
||||
// Supported Draft versions
|
||||
const (
|
||||
Draft4 Draft = 4
|
||||
Draft6 Draft = 6
|
||||
Draft7 Draft = 7
|
||||
Hybrid Draft = math.MaxInt32
|
||||
)
|
||||
|
||||
type draftConfig struct {
|
||||
Version Draft
|
||||
MetaSchemaURL string
|
||||
MetaSchema string
|
||||
}
|
||||
type draftConfigs []draftConfig
|
||||
|
||||
var drafts draftConfigs
|
||||
|
||||
func init() {
|
||||
drafts = []draftConfig{
|
||||
{
|
||||
Version: Draft4,
|
||||
MetaSchemaURL: "http://json-schema.org/draft-04/schema",
|
||||
MetaSchema: `{"id":"http://json-schema.org/draft-04/schema#","$schema":"http://json-schema.org/draft-04/schema#","description":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"positiveInteger":{"type":"integer","minimum":0},"positiveIntegerDefault0":{"allOf":[{"$ref":"#/definitions/positiveInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"minItems":1,"uniqueItems":true}},"type":"object","properties":{"id":{"type":"string"},"$schema":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"default":{},"multipleOf":{"type":"number","minimum":0,"exclusiveMinimum":true},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"boolean","default":false},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"boolean","default":false},"maxLength":{"$ref":"#/definitions/positiveInteger"},"minLength":{"$ref":"#/definitions/positiveIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"anyOf":[{"type":"boolean"},{"$ref":"#"}],"default":{}},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":{}},"maxItems":{"$ref":"#/definitions/positiveInteger"},"minItems":{"$ref":"#/definitions/positiveIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"maxProperties":{"$ref":"#/definitions/positiveInteger"},"minProperties":{"$ref":"#/definitions/positiveIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"anyOf":[{"type":"boolean"},{"$ref":"#"}],"default":{}},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"enum":{"type":"array","minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"dependencies":{"exclusiveMaximum":["maximum"],"exclusiveMinimum":["minimum"]},"default":{}}`,
|
||||
},
|
||||
{
|
||||
Version: Draft6,
|
||||
MetaSchemaURL: "http://json-schema.org/draft-06/schema",
|
||||
MetaSchema: `{"$schema":"http://json-schema.org/draft-06/schema#","$id":"http://json-schema.org/draft-06/schema#","title":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"nonNegativeInteger":{"type":"integer","minimum":0},"nonNegativeIntegerDefault0":{"allOf":[{"$ref":"#/definitions/nonNegativeInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"uniqueItems":true,"default":[]}},"type":["object","boolean"],"properties":{"$id":{"type":"string","format":"uri-reference"},"$schema":{"type":"string","format":"uri"},"$ref":{"type":"string","format":"uri-reference"},"title":{"type":"string"},"description":{"type":"string"},"default":{},"examples":{"type":"array","items":{}},"multipleOf":{"type":"number","exclusiveMinimum":0},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"number"},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"number"},"maxLength":{"$ref":"#/definitions/nonNegativeInteger"},"minLength":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"$ref":"#"},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":{}},"maxItems":{"$ref":"#/definitions/nonNegativeInteger"},"minItems":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"contains":{"$ref":"#"},"maxProperties":{"$ref":"#/definitions/nonNegativeInteger"},"minProperties":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"$ref":"#"},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"propertyNames":{"$ref":"#"},"const":{},"enum":{"type":"array","minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"default":{}}`,
|
||||
},
|
||||
{
|
||||
Version: Draft7,
|
||||
MetaSchemaURL: "http://json-schema.org/draft-07/schema",
|
||||
MetaSchema: `{"$schema":"http://json-schema.org/draft-07/schema#","$id":"http://json-schema.org/draft-07/schema#","title":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"nonNegativeInteger":{"type":"integer","minimum":0},"nonNegativeIntegerDefault0":{"allOf":[{"$ref":"#/definitions/nonNegativeInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"uniqueItems":true,"default":[]}},"type":["object","boolean"],"properties":{"$id":{"type":"string","format":"uri-reference"},"$schema":{"type":"string","format":"uri"},"$ref":{"type":"string","format":"uri-reference"},"$comment":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"default":true,"readOnly":{"type":"boolean","default":false},"examples":{"type":"array","items":true},"multipleOf":{"type":"number","exclusiveMinimum":0},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"number"},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"number"},"maxLength":{"$ref":"#/definitions/nonNegativeInteger"},"minLength":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"$ref":"#"},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":true},"maxItems":{"$ref":"#/definitions/nonNegativeInteger"},"minItems":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"contains":{"$ref":"#"},"maxProperties":{"$ref":"#/definitions/nonNegativeInteger"},"minProperties":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"$ref":"#"},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"propertyNames":{"format":"regex"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"propertyNames":{"$ref":"#"},"const":true,"enum":{"type":"array","items":true,"minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"contentMediaType":{"type":"string"},"contentEncoding":{"type":"string"},"if":{"$ref":"#"},"then":{"$ref":"#"},"else":{"$ref":"#"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"default":true}`,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (dc draftConfigs) GetMetaSchema(url string) string {
|
||||
for _, config := range dc {
|
||||
if config.MetaSchemaURL == url {
|
||||
return config.MetaSchema
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
func (dc draftConfigs) GetDraftVersion(url string) *Draft {
|
||||
for _, config := range dc {
|
||||
if config.MetaSchemaURL == url {
|
||||
return &config.Version
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (dc draftConfigs) GetSchemaURL(draft Draft) string {
|
||||
for _, config := range dc {
|
||||
if config.Version == draft {
|
||||
return config.MetaSchemaURL
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func parseSchemaURL(documentNode any) (string, *Draft, error) {
|
||||
if _, ok := documentNode.(bool); ok {
|
||||
return "", nil, nil
|
||||
}
|
||||
|
||||
m, ok := documentNode.(map[string]any)
|
||||
if !ok {
|
||||
return "", nil, errors.New("schema is invalid")
|
||||
}
|
||||
|
||||
if v, ok := m[KeySchema]; ok {
|
||||
s, ok := v.(string)
|
||||
if !ok {
|
||||
return "", nil, errors.New(formatErrorDescription(
|
||||
Locale.MustBeOfType(),
|
||||
ErrorDetails{
|
||||
"key": KeySchema,
|
||||
"type": TypeString,
|
||||
}))
|
||||
}
|
||||
|
||||
schemaReference, err := gojsonreference.NewJsonReference(s)
|
||||
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
schema := schemaReference.String()
|
||||
|
||||
return schema, drafts.GetDraftVersion(schema), nil
|
||||
}
|
||||
|
||||
return "", nil, nil
|
||||
}
|
||||
+366
@@ -0,0 +1,366 @@
|
||||
// nolint: goconst // String duplication will be handled later by using errors.Is.
|
||||
package gojsonschema
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var errorTemplates = errorTemplate{template.New("errors-new"), sync.RWMutex{}}
|
||||
|
||||
// template.Template is not thread-safe for writing, so some locking is done
|
||||
// sync.RWMutex is used for efficiently locking when new templates are created
|
||||
type errorTemplate struct {
|
||||
*template.Template
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
type (
|
||||
|
||||
// FalseError indicates that -
|
||||
// ErrorDetails: -
|
||||
FalseError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// RequiredError indicates that a required field is missing
|
||||
// ErrorDetails: property string
|
||||
RequiredError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// InvalidTypeError indicates that a field has the incorrect type
|
||||
// ErrorDetails: expected, given
|
||||
InvalidTypeError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// NumberAnyOfError is produced in case of a failing "anyOf" validation
|
||||
// ErrorDetails: -
|
||||
NumberAnyOfError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// NumberOneOfError is produced in case of a failing "oneOf" validation
|
||||
// ErrorDetails: -
|
||||
NumberOneOfError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// NumberAllOfError is produced in case of a failing "allOf" validation
|
||||
// ErrorDetails: -
|
||||
NumberAllOfError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// NumberNotError is produced if a "not" validation failed
|
||||
// ErrorDetails: -
|
||||
NumberNotError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// MissingDependencyError is produced in case of a "missing dependency" problem
|
||||
// ErrorDetails: dependency
|
||||
MissingDependencyError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// InternalError indicates an internal error
|
||||
// ErrorDetails: error
|
||||
InternalError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// ConstError indicates a const error
|
||||
// ErrorDetails: allowed
|
||||
ConstError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// EnumError indicates an enum error
|
||||
// ErrorDetails: allowed
|
||||
EnumError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// ArrayNoAdditionalItemsError is produced if additional items were found, but not allowed
|
||||
// ErrorDetails: -
|
||||
ArrayNoAdditionalItemsError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// ArrayMinItemsError is produced if an array contains less items than the allowed minimum
|
||||
// ErrorDetails: min
|
||||
ArrayMinItemsError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// ArrayMaxItemsError is produced if an array contains more items than the allowed maximum
|
||||
// ErrorDetails: max
|
||||
ArrayMaxItemsError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// ItemsMustBeUniqueError is produced if an array requires unique items, but contains non-unique items
|
||||
// ErrorDetails: type, i, j
|
||||
ItemsMustBeUniqueError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// ArrayContainsError is produced if an array contains invalid items
|
||||
// ErrorDetails:
|
||||
ArrayContainsError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// ArrayMinPropertiesError is produced if an object contains less properties than the allowed minimum
|
||||
// ErrorDetails: min
|
||||
ArrayMinPropertiesError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// ArrayMaxPropertiesError is produced if an object contains more properties than the allowed maximum
|
||||
// ErrorDetails: max
|
||||
ArrayMaxPropertiesError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// AdditionalPropertyNotAllowedError is produced if an object has additional properties, but not allowed
|
||||
// ErrorDetails: property
|
||||
AdditionalPropertyNotAllowedError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// InvalidPropertyPatternError is produced if an pattern was found
|
||||
// ErrorDetails: property, pattern
|
||||
InvalidPropertyPatternError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// InvalidPropertyNameError is produced if an invalid-named property was found
|
||||
// ErrorDetails: property
|
||||
InvalidPropertyNameError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// StringLengthGTEError is produced if a string is shorter than the minimum required length
|
||||
// ErrorDetails: min
|
||||
StringLengthGTEError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// StringLengthLTEError is produced if a string is longer than the maximum allowed length
|
||||
// ErrorDetails: max
|
||||
StringLengthLTEError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// DoesNotMatchPatternError is produced if a string does not match the defined pattern
|
||||
// ErrorDetails: pattern
|
||||
DoesNotMatchPatternError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// DoesNotMatchFormatError is produced if a string does not match the defined format
|
||||
// ErrorDetails: format
|
||||
DoesNotMatchFormatError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// MultipleOfError is produced if a number is not a multiple of the defined multipleOf
|
||||
// ErrorDetails: multiple
|
||||
MultipleOfError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// NumberGTEError is produced if a number is lower than the allowed minimum
|
||||
// ErrorDetails: min
|
||||
NumberGTEError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// NumberGTError is produced if a number is lower than, or equal to the specified minimum, and exclusiveMinimum is set
|
||||
// ErrorDetails: min
|
||||
NumberGTError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// NumberLTEError is produced if a number is higher than the allowed maximum
|
||||
// ErrorDetails: max
|
||||
NumberLTEError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// NumberLTError is produced if a number is higher than, or equal to the specified maximum, and exclusiveMaximum is set
|
||||
// ErrorDetails: max
|
||||
NumberLTError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// ConditionThenError is produced if a condition's "then" validation is invalid
|
||||
// ErrorDetails: -
|
||||
ConditionThenError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
|
||||
// ConditionElseError is produced if a condition's "else" condition is invalid
|
||||
// ErrorDetails: -
|
||||
ConditionElseError struct {
|
||||
ResultErrorFields
|
||||
}
|
||||
)
|
||||
|
||||
// newError takes a ResultError type and sets the type, context, description, details, value, and field
|
||||
func newError(err ResultError, context *JSONContext, value any, locale locale, details ErrorDetails) {
|
||||
var t string
|
||||
var d string
|
||||
switch err.(type) {
|
||||
case *FalseError:
|
||||
t = "false"
|
||||
d = locale.False()
|
||||
case *RequiredError:
|
||||
t = "required"
|
||||
d = locale.Required()
|
||||
case *InvalidTypeError:
|
||||
t = "invalid_type"
|
||||
d = locale.InvalidType()
|
||||
case *NumberAnyOfError:
|
||||
t = "number_any_of"
|
||||
d = locale.NumberAnyOf()
|
||||
case *NumberOneOfError:
|
||||
t = "number_one_of"
|
||||
d = locale.NumberOneOf()
|
||||
case *NumberAllOfError:
|
||||
t = "number_all_of"
|
||||
d = locale.NumberAllOf()
|
||||
case *NumberNotError:
|
||||
t = "number_not"
|
||||
d = locale.NumberNot()
|
||||
case *MissingDependencyError:
|
||||
t = "missing_dependency"
|
||||
d = locale.MissingDependency()
|
||||
case *InternalError:
|
||||
t = "internal"
|
||||
d = locale.Internal()
|
||||
case *ConstError:
|
||||
t = "const"
|
||||
d = locale.Const()
|
||||
case *EnumError:
|
||||
t = "enum"
|
||||
d = locale.Enum()
|
||||
case *ArrayNoAdditionalItemsError:
|
||||
t = "array_no_additional_items"
|
||||
d = locale.ArrayNoAdditionalItems()
|
||||
case *ArrayMinItemsError:
|
||||
t = "array_min_items"
|
||||
d = locale.ArrayMinItems()
|
||||
case *ArrayMaxItemsError:
|
||||
t = "array_max_items"
|
||||
d = locale.ArrayMaxItems()
|
||||
case *ItemsMustBeUniqueError:
|
||||
t = "unique"
|
||||
d = locale.Unique()
|
||||
case *ArrayContainsError:
|
||||
t = "contains"
|
||||
d = locale.ArrayContains()
|
||||
case *ArrayMinPropertiesError:
|
||||
t = "array_min_properties"
|
||||
d = locale.ArrayMinProperties()
|
||||
case *ArrayMaxPropertiesError:
|
||||
t = "array_max_properties"
|
||||
d = locale.ArrayMaxProperties()
|
||||
case *AdditionalPropertyNotAllowedError:
|
||||
t = "additional_property_not_allowed"
|
||||
d = locale.AdditionalPropertyNotAllowed()
|
||||
case *InvalidPropertyPatternError:
|
||||
t = "invalid_property_pattern"
|
||||
d = locale.InvalidPropertyPattern()
|
||||
case *InvalidPropertyNameError:
|
||||
t = "invalid_property_name"
|
||||
d = locale.InvalidPropertyName()
|
||||
case *StringLengthGTEError:
|
||||
t = "string_gte"
|
||||
d = locale.StringGTE()
|
||||
case *StringLengthLTEError:
|
||||
t = "string_lte"
|
||||
d = locale.StringLTE()
|
||||
case *DoesNotMatchPatternError:
|
||||
t = "pattern"
|
||||
d = locale.DoesNotMatchPattern()
|
||||
case *DoesNotMatchFormatError:
|
||||
t = "format"
|
||||
d = locale.DoesNotMatchFormat()
|
||||
case *MultipleOfError:
|
||||
t = "multiple_of"
|
||||
d = locale.MultipleOf()
|
||||
case *NumberGTEError:
|
||||
t = "number_gte"
|
||||
d = locale.NumberGTE()
|
||||
case *NumberGTError:
|
||||
t = "number_gt"
|
||||
d = locale.NumberGT()
|
||||
case *NumberLTEError:
|
||||
t = "number_lte"
|
||||
d = locale.NumberLTE()
|
||||
case *NumberLTError:
|
||||
t = "number_lt"
|
||||
d = locale.NumberLT()
|
||||
case *ConditionThenError:
|
||||
t = "condition_then"
|
||||
d = locale.ConditionThen()
|
||||
case *ConditionElseError:
|
||||
t = "condition_else"
|
||||
d = locale.ConditionElse()
|
||||
}
|
||||
|
||||
err.SetType(t)
|
||||
err.SetContext(context)
|
||||
err.SetValue(value)
|
||||
err.SetDetails(details)
|
||||
err.SetDescriptionFormat(d)
|
||||
details["field"] = err.Field()
|
||||
|
||||
if _, exists := details["context"]; !exists && context != nil {
|
||||
details["context"] = context.String()
|
||||
}
|
||||
|
||||
err.SetDescription(formatErrorDescription(err.DescriptionFormat(), details))
|
||||
}
|
||||
|
||||
// formatErrorDescription takes a string in the default text/template
|
||||
// format and converts it to a string with replacements. The fields come
|
||||
// from the ErrorDetails struct and vary for each type of error.
|
||||
func formatErrorDescription(s string, details ErrorDetails) string {
|
||||
|
||||
var tpl *template.Template
|
||||
var descrAsBuffer bytes.Buffer
|
||||
var err error
|
||||
|
||||
errorTemplates.RLock()
|
||||
tpl = errorTemplates.Lookup(s)
|
||||
errorTemplates.RUnlock()
|
||||
|
||||
if tpl == nil {
|
||||
errorTemplates.Lock()
|
||||
tpl = errorTemplates.New(s)
|
||||
|
||||
if ErrorTemplateFuncs != nil {
|
||||
tpl.Funcs(ErrorTemplateFuncs)
|
||||
}
|
||||
|
||||
tpl, err = tpl.Parse(s)
|
||||
errorTemplates.Unlock()
|
||||
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
err = tpl.Execute(&descrAsBuffer, details)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
return descrAsBuffer.String()
|
||||
}
|
||||
Generated
Vendored
+368
@@ -0,0 +1,368 @@
|
||||
package gojsonschema
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/mail"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
// FormatChecker is the interface all formatters added to FormatCheckerChain must implement
|
||||
FormatChecker interface {
|
||||
// IsFormat checks if input has the correct format
|
||||
IsFormat(input any) bool
|
||||
}
|
||||
|
||||
// FormatCheckerChain holds the formatters
|
||||
FormatCheckerChain struct {
|
||||
formatters map[string]FormatChecker
|
||||
}
|
||||
|
||||
// EmailFormatChecker verifies email address formats
|
||||
EmailFormatChecker struct{}
|
||||
|
||||
// IPV4FormatChecker verifies IP addresses in the IPv4 format
|
||||
IPV4FormatChecker struct{}
|
||||
|
||||
// IPV6FormatChecker verifies IP addresses in the IPv6 format
|
||||
IPV6FormatChecker struct{}
|
||||
|
||||
// DateTimeFormatChecker verifies date/time formats per RFC3339 5.6
|
||||
//
|
||||
// Valid formats:
|
||||
// Partial Time: HH:MM:SS
|
||||
// Full Date: YYYY-MM-DD
|
||||
// Full Time: HH:MM:SSZ-07:00
|
||||
// Date Time: YYYY-MM-DDTHH:MM:SSZ-0700
|
||||
//
|
||||
// Where
|
||||
// YYYY = 4DIGIT year
|
||||
// MM = 2DIGIT month ; 01-12
|
||||
// DD = 2DIGIT day-month ; 01-28, 01-29, 01-30, 01-31 based on month/year
|
||||
// HH = 2DIGIT hour ; 00-23
|
||||
// MM = 2DIGIT ; 00-59
|
||||
// SS = 2DIGIT ; 00-58, 00-60 based on leap second rules
|
||||
// T = Literal
|
||||
// Z = Literal
|
||||
//
|
||||
// Note: Nanoseconds are also suported in all formats
|
||||
//
|
||||
// http://tools.ietf.org/html/rfc3339#section-5.6
|
||||
DateTimeFormatChecker struct{}
|
||||
|
||||
// DateFormatChecker verifies date formats
|
||||
//
|
||||
// Valid format:
|
||||
// Full Date: YYYY-MM-DD
|
||||
//
|
||||
// Where
|
||||
// YYYY = 4DIGIT year
|
||||
// MM = 2DIGIT month ; 01-12
|
||||
// DD = 2DIGIT day-month ; 01-28, 01-29, 01-30, 01-31 based on month/year
|
||||
DateFormatChecker struct{}
|
||||
|
||||
// TimeFormatChecker verifies time formats
|
||||
//
|
||||
// Valid formats:
|
||||
// Partial Time: HH:MM:SS
|
||||
// Full Time: HH:MM:SSZ-07:00
|
||||
//
|
||||
// Where
|
||||
// HH = 2DIGIT hour ; 00-23
|
||||
// MM = 2DIGIT ; 00-59
|
||||
// SS = 2DIGIT ; 00-58, 00-60 based on leap second rules
|
||||
// T = Literal
|
||||
// Z = Literal
|
||||
TimeFormatChecker struct{}
|
||||
|
||||
// URIFormatChecker validates a URI with a valid Scheme per RFC3986
|
||||
URIFormatChecker struct{}
|
||||
|
||||
// URIReferenceFormatChecker validates a URI or relative-reference per RFC3986
|
||||
URIReferenceFormatChecker struct{}
|
||||
|
||||
// URITemplateFormatChecker validates a URI template per RFC6570
|
||||
URITemplateFormatChecker struct{}
|
||||
|
||||
// HostnameFormatChecker validates a hostname is in the correct format
|
||||
HostnameFormatChecker struct{}
|
||||
|
||||
// UUIDFormatChecker validates a UUID is in the correct format
|
||||
UUIDFormatChecker struct{}
|
||||
|
||||
// RegexFormatChecker validates a regex is in the correct format
|
||||
RegexFormatChecker struct{}
|
||||
|
||||
// JSONPointerFormatChecker validates a JSON Pointer per RFC6901
|
||||
JSONPointerFormatChecker struct{}
|
||||
|
||||
// RelativeJSONPointerFormatChecker validates a relative JSON Pointer is in the correct format
|
||||
RelativeJSONPointerFormatChecker struct{}
|
||||
)
|
||||
|
||||
var (
|
||||
// FormatCheckers holds the valid formatters, and is a public variable
|
||||
// so library users can add custom formatters
|
||||
FormatCheckers = FormatCheckerChain{
|
||||
formatters: map[string]FormatChecker{
|
||||
"date": DateFormatChecker{},
|
||||
"time": TimeFormatChecker{},
|
||||
"date-time": DateTimeFormatChecker{},
|
||||
"hostname": HostnameFormatChecker{},
|
||||
"email": EmailFormatChecker{},
|
||||
"idn-email": EmailFormatChecker{},
|
||||
"ipv4": IPV4FormatChecker{},
|
||||
"ipv6": IPV6FormatChecker{},
|
||||
"uri": URIFormatChecker{},
|
||||
"uri-reference": URIReferenceFormatChecker{},
|
||||
"iri": URIFormatChecker{},
|
||||
"iri-reference": URIReferenceFormatChecker{},
|
||||
"uri-template": URITemplateFormatChecker{},
|
||||
"uuid": UUIDFormatChecker{},
|
||||
"regex": RegexFormatChecker{},
|
||||
"json-pointer": JSONPointerFormatChecker{},
|
||||
"relative-json-pointer": RelativeJSONPointerFormatChecker{},
|
||||
},
|
||||
}
|
||||
|
||||
// Regex credit: https://www.socketloop.com/tutorials/golang-validate-hostname
|
||||
rxHostname = regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$`)
|
||||
|
||||
// Use a regex to make sure curly brackets are balanced properly after validating it as a AURI
|
||||
rxURITemplate = regexp.MustCompile("^([^{]*({[^}]*})?)*$")
|
||||
|
||||
rxUUID = regexp.MustCompile("^(?i)[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$")
|
||||
|
||||
rxJSONPointer = regexp.MustCompile("^(?:/(?:[^~/]|~0|~1)*)*$")
|
||||
|
||||
rxRelJSONPointer = regexp.MustCompile("^(?:0|[1-9][0-9]*)(?:#|(?:/(?:[^~/]|~0|~1)*)*)$")
|
||||
|
||||
lock = new(sync.RWMutex)
|
||||
)
|
||||
|
||||
// Add adds a FormatChecker to the FormatCheckerChain
|
||||
// The name used will be the value used for the format key in your json schema
|
||||
func (c *FormatCheckerChain) Add(name string, f FormatChecker) *FormatCheckerChain {
|
||||
lock.Lock()
|
||||
c.formatters[name] = f
|
||||
lock.Unlock()
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Remove deletes a FormatChecker from the FormatCheckerChain (if it exists)
|
||||
func (c *FormatCheckerChain) Remove(name string) *FormatCheckerChain {
|
||||
lock.Lock()
|
||||
delete(c.formatters, name)
|
||||
lock.Unlock()
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Has checks to see if the FormatCheckerChain holds a FormatChecker with the given name
|
||||
func (c *FormatCheckerChain) Has(name string) bool {
|
||||
lock.RLock()
|
||||
_, ok := c.formatters[name]
|
||||
lock.RUnlock()
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsFormat will check an input against a FormatChecker with the given name
|
||||
// to see if it is the correct format
|
||||
func (c *FormatCheckerChain) IsFormat(name string, input any) bool {
|
||||
lock.RLock()
|
||||
f, ok := c.formatters[name]
|
||||
lock.RUnlock()
|
||||
|
||||
// If a format is unrecognized it should always pass validation
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
return f.IsFormat(input)
|
||||
}
|
||||
|
||||
// IsFormat checks if input is a correctly formatted e-mail address
|
||||
func (f EmailFormatChecker) IsFormat(input any) bool {
|
||||
asString, ok := input.(string)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
_, err := mail.ParseAddress(asString)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsFormat checks if input is a correctly formatted IPv4-address
|
||||
func (f IPV4FormatChecker) IsFormat(input any) bool {
|
||||
asString, ok := input.(string)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// Credit: https://github.com/asaskevich/govalidator
|
||||
ip := net.ParseIP(asString)
|
||||
return ip != nil && strings.Contains(asString, ".")
|
||||
}
|
||||
|
||||
// IsFormat checks if input is a correctly formatted IPv6=address
|
||||
func (f IPV6FormatChecker) IsFormat(input any) bool {
|
||||
asString, ok := input.(string)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// Credit: https://github.com/asaskevich/govalidator
|
||||
ip := net.ParseIP(asString)
|
||||
return ip != nil && strings.Contains(asString, ":")
|
||||
}
|
||||
|
||||
// IsFormat checks if input is a correctly formatted date/time per RFC3339 5.6
|
||||
func (f DateTimeFormatChecker) IsFormat(input any) bool {
|
||||
asString, ok := input.(string)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
formats := []string{
|
||||
"15:04:05",
|
||||
"15:04:05Z07:00",
|
||||
"2006-01-02",
|
||||
time.RFC3339,
|
||||
time.RFC3339Nano,
|
||||
}
|
||||
|
||||
for _, format := range formats {
|
||||
if _, err := time.Parse(format, asString); err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsFormat checks if input is a correctly formatted date (YYYY-MM-DD)
|
||||
func (f DateFormatChecker) IsFormat(input any) bool {
|
||||
asString, ok := input.(string)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
_, err := time.Parse("2006-01-02", asString)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsFormat checks if input correctly formatted time (HH:MM:SS or HH:MM:SSZ-07:00)
|
||||
func (f TimeFormatChecker) IsFormat(input any) bool {
|
||||
asString, ok := input.(string)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
if _, err := time.Parse("15:04:05Z07:00", asString); err == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
_, err := time.Parse("15:04:05", asString)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsFormat checks if input is correctly formatted URI with a valid Scheme per RFC3986
|
||||
func (f URIFormatChecker) IsFormat(input any) bool {
|
||||
asString, ok := input.(string)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
u, err := url.Parse(asString)
|
||||
|
||||
if err != nil || u.Scheme == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
return !strings.Contains(asString, `\`)
|
||||
}
|
||||
|
||||
// IsFormat checks if input is a correctly formatted URI or relative-reference per RFC3986
|
||||
func (f URIReferenceFormatChecker) IsFormat(input any) bool {
|
||||
asString, ok := input.(string)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
_, err := url.Parse(asString)
|
||||
return err == nil && !strings.Contains(asString, `\`)
|
||||
}
|
||||
|
||||
// IsFormat checks if input is a correctly formatted URI template per RFC6570
|
||||
func (f URITemplateFormatChecker) IsFormat(input any) bool {
|
||||
asString, ok := input.(string)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
u, err := url.Parse(asString)
|
||||
if err != nil || strings.Contains(asString, `\`) {
|
||||
return false
|
||||
}
|
||||
|
||||
return rxURITemplate.MatchString(u.Path)
|
||||
}
|
||||
|
||||
// IsFormat checks if input is a correctly formatted hostname
|
||||
func (f HostnameFormatChecker) IsFormat(input any) bool {
|
||||
asString, ok := input.(string)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
return rxHostname.MatchString(asString) && len(asString) < 256
|
||||
}
|
||||
|
||||
// IsFormat checks if input is a correctly formatted UUID
|
||||
func (f UUIDFormatChecker) IsFormat(input any) bool {
|
||||
asString, ok := input.(string)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
return rxUUID.MatchString(asString)
|
||||
}
|
||||
|
||||
// IsFormat checks if input is a correctly formatted regular expression
|
||||
func (f RegexFormatChecker) IsFormat(input any) bool {
|
||||
asString, ok := input.(string)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
if asString == "" {
|
||||
return true
|
||||
}
|
||||
_, err := regexp.Compile(asString)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsFormat checks if input is a correctly formatted JSON Pointer per RFC6901
|
||||
func (f JSONPointerFormatChecker) IsFormat(input any) bool {
|
||||
asString, ok := input.(string)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
return rxJSONPointer.MatchString(asString)
|
||||
}
|
||||
|
||||
// IsFormat checks if input is a correctly formatted relative JSON Pointer
|
||||
func (f RelativeJSONPointerFormatChecker) IsFormat(input any) bool {
|
||||
asString, ok := input.(string)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
return rxRelJSONPointer.MatchString(asString)
|
||||
}
|
||||
Generated
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
// Copyright 2015 xeipuuv ( https://github.com/xeipuuv )
|
||||
//
|
||||
// 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.
|
||||
|
||||
// author xeipuuv
|
||||
// author-github https://github.com/xeipuuv
|
||||
// author-mail xeipuuv@gmail.com
|
||||
//
|
||||
// repository-name gojsonschema
|
||||
// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language.
|
||||
//
|
||||
// description Very simple log wrapper.
|
||||
// Used for debugging/testing purposes.
|
||||
//
|
||||
// created 01-01-2015
|
||||
|
||||
package gojsonschema
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
const internalLogEnabled = false
|
||||
|
||||
func internalLog(format string, v ...any) {
|
||||
log.Printf(format, v...)
|
||||
}
|
||||
Generated
Vendored
+73
@@ -0,0 +1,73 @@
|
||||
// Copyright 2013 MongoDB, Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
// author tolsen
|
||||
// author-github https://github.com/tolsen
|
||||
//
|
||||
// repository-name gojsonschema
|
||||
// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language.
|
||||
//
|
||||
// description Implements a persistent (immutable w/ shared structure) singly-linked list of strings for the purpose of storing a json context
|
||||
//
|
||||
// created 04-09-2013
|
||||
|
||||
package gojsonschema
|
||||
|
||||
import "bytes"
|
||||
|
||||
// JSONContext implements a persistent linked-list of strings
|
||||
type JSONContext struct {
|
||||
head string
|
||||
tail *JSONContext
|
||||
}
|
||||
|
||||
// NewJSONContext creates a new JSONContext
|
||||
func NewJSONContext(head string, tail *JSONContext) *JSONContext {
|
||||
return &JSONContext{head, tail}
|
||||
}
|
||||
|
||||
// String displays the context in reverse.
|
||||
// This plays well with the data structure's persistent nature with
|
||||
// Cons and a json document's tree structure.
|
||||
func (c *JSONContext) String(del ...string) string {
|
||||
byteArr := make([]byte, 0, c.stringLen())
|
||||
buf := bytes.NewBuffer(byteArr)
|
||||
c.writeStringToBuffer(buf, del)
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (c *JSONContext) stringLen() int {
|
||||
length := 0
|
||||
if c.tail != nil {
|
||||
length = c.tail.stringLen() + 1 // add 1 for "."
|
||||
}
|
||||
|
||||
length += len(c.head)
|
||||
return length
|
||||
}
|
||||
|
||||
func (c *JSONContext) writeStringToBuffer(buf *bytes.Buffer, del []string) {
|
||||
if c.tail != nil {
|
||||
c.tail.writeStringToBuffer(buf, del)
|
||||
|
||||
if len(del) > 0 {
|
||||
buf.WriteString(del[0])
|
||||
} else {
|
||||
buf.WriteString(".")
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString(c.head)
|
||||
}
|
||||
Generated
Vendored
+410
@@ -0,0 +1,410 @@
|
||||
// Copyright 2015 xeipuuv ( https://github.com/xeipuuv )
|
||||
//
|
||||
// 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.
|
||||
|
||||
// author xeipuuv
|
||||
// author-github https://github.com/xeipuuv
|
||||
// author-mail xeipuuv@gmail.com
|
||||
//
|
||||
// repository-name gojsonschema
|
||||
// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language.
|
||||
//
|
||||
// description Different strategies to load JSON files.
|
||||
// Includes References (file and HTTP), JSON strings and Go types.
|
||||
//
|
||||
// created 01-02-2015
|
||||
|
||||
package gojsonschema
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/xeipuuv/gojsonreference"
|
||||
)
|
||||
|
||||
// NOTE(sr): We need to control from which hosts remote references are
|
||||
// allowed to be resolved via HTTP requests. It's quite cumbersome to
|
||||
// add extra parameters to all calls and interfaces involved, so we're
|
||||
// using a global variable instead:
|
||||
var allowNet map[string]struct{}
|
||||
var netMut sync.RWMutex
|
||||
|
||||
func SetAllowNet(hosts []string) {
|
||||
netMut.Lock()
|
||||
defer netMut.Unlock()
|
||||
if hosts == nil {
|
||||
allowNet = nil // resetting the global
|
||||
return
|
||||
}
|
||||
allowNet = make(map[string]struct{}, len(hosts))
|
||||
for _, host := range hosts {
|
||||
allowNet[host] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func isAllowed(ref *url.URL) bool {
|
||||
netMut.RLock()
|
||||
defer netMut.RUnlock()
|
||||
if allowNet == nil {
|
||||
return true
|
||||
}
|
||||
_, ok := allowNet[ref.Hostname()]
|
||||
return ok
|
||||
}
|
||||
|
||||
var osFS = osFileSystem(os.Open)
|
||||
|
||||
// JSONLoader defines the JSON loader interface
|
||||
type JSONLoader interface {
|
||||
JSONSource() any
|
||||
LoadJSON() (any, error)
|
||||
JSONReference() (gojsonreference.JsonReference, error)
|
||||
LoaderFactory() JSONLoaderFactory
|
||||
}
|
||||
|
||||
// JSONLoaderFactory defines the JSON loader factory interface
|
||||
type JSONLoaderFactory interface {
|
||||
// New creates a new JSON loader for the given source
|
||||
New(source string) JSONLoader
|
||||
}
|
||||
|
||||
// DefaultJSONLoaderFactory is the default JSON loader factory
|
||||
type DefaultJSONLoaderFactory struct {
|
||||
}
|
||||
|
||||
// FileSystemJSONLoaderFactory is a JSON loader factory that uses http.FileSystem
|
||||
type FileSystemJSONLoaderFactory struct {
|
||||
fs http.FileSystem
|
||||
}
|
||||
|
||||
// New creates a new JSON loader for the given source
|
||||
func (d DefaultJSONLoaderFactory) New(source string) JSONLoader {
|
||||
return &jsonReferenceLoader{
|
||||
fs: osFS,
|
||||
source: source,
|
||||
}
|
||||
}
|
||||
|
||||
// New creates a new JSON loader for the given source
|
||||
func (f FileSystemJSONLoaderFactory) New(source string) JSONLoader {
|
||||
return &jsonReferenceLoader{
|
||||
fs: f.fs,
|
||||
source: source,
|
||||
}
|
||||
}
|
||||
|
||||
// osFileSystem is a functional wrapper for os.Open that implements http.FileSystem.
|
||||
type osFileSystem func(string) (*os.File, error)
|
||||
|
||||
// Opens a file with the given name
|
||||
func (o osFileSystem) Open(name string) (http.File, error) {
|
||||
return o(name)
|
||||
}
|
||||
|
||||
// JSON Reference loader
|
||||
// references are used to load JSONs from files and HTTP
|
||||
|
||||
type jsonReferenceLoader struct {
|
||||
fs http.FileSystem
|
||||
source string
|
||||
}
|
||||
|
||||
func (l *jsonReferenceLoader) JSONSource() any {
|
||||
return l.source
|
||||
}
|
||||
|
||||
func (l *jsonReferenceLoader) JSONReference() (gojsonreference.JsonReference, error) {
|
||||
return gojsonreference.NewJsonReference(l.JSONSource().(string))
|
||||
}
|
||||
|
||||
func (l *jsonReferenceLoader) LoaderFactory() JSONLoaderFactory {
|
||||
return &FileSystemJSONLoaderFactory{
|
||||
fs: l.fs,
|
||||
}
|
||||
}
|
||||
|
||||
// NewReferenceLoader returns a JSON reference loader using the given source and the local OS file system.
|
||||
func NewReferenceLoader(source string) JSONLoader {
|
||||
return &jsonReferenceLoader{
|
||||
fs: osFS,
|
||||
source: source,
|
||||
}
|
||||
}
|
||||
|
||||
// NewReferenceLoaderFileSystem returns a JSON reference loader using the given source and file system.
|
||||
func NewReferenceLoaderFileSystem(source string, fs http.FileSystem) JSONLoader {
|
||||
return &jsonReferenceLoader{
|
||||
fs: fs,
|
||||
source: source,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *jsonReferenceLoader) LoadJSON() (any, error) {
|
||||
|
||||
var err error
|
||||
|
||||
reference, err := gojsonreference.NewJsonReference(l.JSONSource().(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
refToURL := reference
|
||||
refToURL.GetUrl().Fragment = ""
|
||||
|
||||
if reference.HasFileScheme {
|
||||
|
||||
filename := strings.TrimPrefix(refToURL.String(), "file://")
|
||||
filename, err = url.QueryUnescape(filename)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
// on Windows, a file URL may have an extra leading slash, use slashes
|
||||
// instead of backslashes, and have spaces escaped
|
||||
filename = strings.TrimPrefix(filename, "/")
|
||||
filename = filepath.FromSlash(filename)
|
||||
}
|
||||
|
||||
return l.loadFromFile(filename)
|
||||
}
|
||||
|
||||
// NOTE(sr): hardcoded metaschema references are not subject to allow_net
|
||||
// checking; their contents are hardcoded in the library!
|
||||
//
|
||||
// returned cached versions for metaschemas for drafts 4, 6 and 7
|
||||
// for performance and allow for easier offline use
|
||||
if metaSchema := drafts.GetMetaSchema(refToURL.String()); metaSchema != "" {
|
||||
return decodeJSONUsingNumber(strings.NewReader(metaSchema))
|
||||
}
|
||||
|
||||
if isAllowed(refToURL.GetUrl()) {
|
||||
return l.loadFromHTTP(refToURL.String())
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("remote reference loading disabled: %s", reference.String())
|
||||
}
|
||||
|
||||
func (l *jsonReferenceLoader) loadFromHTTP(address string) (any, error) {
|
||||
|
||||
resp, err := http.Get(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// must return HTTP Status 200 OK
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errors.New(formatErrorDescription(Locale.HTTPBadStatus(), ErrorDetails{"status": resp.Status}))
|
||||
}
|
||||
|
||||
bodyBuff, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return decodeJSONUsingNumber(bytes.NewReader(bodyBuff))
|
||||
}
|
||||
|
||||
func (l *jsonReferenceLoader) loadFromFile(path string) (any, error) {
|
||||
f, err := l.fs.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
bodyBuff, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return decodeJSONUsingNumber(bytes.NewReader(bodyBuff))
|
||||
|
||||
}
|
||||
|
||||
// JSON string loader
|
||||
|
||||
type jsonStringLoader struct {
|
||||
source string
|
||||
}
|
||||
|
||||
func (l *jsonStringLoader) JSONSource() any {
|
||||
return l.source
|
||||
}
|
||||
|
||||
func (l *jsonStringLoader) JSONReference() (gojsonreference.JsonReference, error) {
|
||||
return gojsonreference.NewJsonReference("#")
|
||||
}
|
||||
|
||||
func (l *jsonStringLoader) LoaderFactory() JSONLoaderFactory {
|
||||
return &DefaultJSONLoaderFactory{}
|
||||
}
|
||||
|
||||
// NewStringLoader creates a new JSONLoader, taking a string as source
|
||||
func NewStringLoader(source string) JSONLoader {
|
||||
return &jsonStringLoader{source: source}
|
||||
}
|
||||
|
||||
func (l *jsonStringLoader) LoadJSON() (any, error) {
|
||||
|
||||
return decodeJSONUsingNumber(strings.NewReader(l.JSONSource().(string)))
|
||||
|
||||
}
|
||||
|
||||
// JSON bytes loader
|
||||
|
||||
type jsonBytesLoader struct {
|
||||
source []byte
|
||||
}
|
||||
|
||||
func (l *jsonBytesLoader) JSONSource() any {
|
||||
return l.source
|
||||
}
|
||||
|
||||
func (l *jsonBytesLoader) JSONReference() (gojsonreference.JsonReference, error) {
|
||||
return gojsonreference.NewJsonReference("#")
|
||||
}
|
||||
|
||||
func (l *jsonBytesLoader) LoaderFactory() JSONLoaderFactory {
|
||||
return &DefaultJSONLoaderFactory{}
|
||||
}
|
||||
|
||||
// NewBytesLoader creates a new JSONLoader, taking a `[]byte` as source
|
||||
func NewBytesLoader(source []byte) JSONLoader {
|
||||
return &jsonBytesLoader{source: source}
|
||||
}
|
||||
|
||||
func (l *jsonBytesLoader) LoadJSON() (any, error) {
|
||||
return decodeJSONUsingNumber(bytes.NewReader(l.JSONSource().([]byte)))
|
||||
}
|
||||
|
||||
// JSON Go (types) loader
|
||||
// used to load JSONs from the code as maps, any, structs ...
|
||||
|
||||
type jsonGoLoader struct {
|
||||
source any
|
||||
}
|
||||
|
||||
func (l *jsonGoLoader) JSONSource() any {
|
||||
return l.source
|
||||
}
|
||||
|
||||
func (l *jsonGoLoader) JSONReference() (gojsonreference.JsonReference, error) {
|
||||
return gojsonreference.NewJsonReference("#")
|
||||
}
|
||||
|
||||
func (l *jsonGoLoader) LoaderFactory() JSONLoaderFactory {
|
||||
return &DefaultJSONLoaderFactory{}
|
||||
}
|
||||
|
||||
// NewGoLoader creates a new JSONLoader from a given Go struct
|
||||
func NewGoLoader(source any) JSONLoader {
|
||||
return &jsonGoLoader{source: source}
|
||||
}
|
||||
|
||||
func (l *jsonGoLoader) LoadJSON() (any, error) {
|
||||
|
||||
// convert it to a compliant JSON first to avoid types "mismatches"
|
||||
|
||||
jsonBytes, err := json.Marshal(l.JSONSource())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return decodeJSONUsingNumber(bytes.NewReader(jsonBytes))
|
||||
|
||||
}
|
||||
|
||||
type jsonIOLoader struct {
|
||||
buf *bytes.Buffer
|
||||
}
|
||||
|
||||
// NewReaderLoader creates a new JSON loader using the provided io.Reader
|
||||
func NewReaderLoader(source io.Reader) (JSONLoader, io.Reader) {
|
||||
buf := &bytes.Buffer{}
|
||||
return &jsonIOLoader{buf: buf}, io.TeeReader(source, buf)
|
||||
}
|
||||
|
||||
// NewWriterLoader creates a new JSON loader using the provided io.Writer
|
||||
func NewWriterLoader(source io.Writer) (JSONLoader, io.Writer) {
|
||||
buf := &bytes.Buffer{}
|
||||
return &jsonIOLoader{buf: buf}, io.MultiWriter(source, buf)
|
||||
}
|
||||
|
||||
func (l *jsonIOLoader) JSONSource() any {
|
||||
return l.buf.String()
|
||||
}
|
||||
|
||||
func (l *jsonIOLoader) LoadJSON() (any, error) {
|
||||
return decodeJSONUsingNumber(l.buf)
|
||||
}
|
||||
|
||||
func (l *jsonIOLoader) JSONReference() (gojsonreference.JsonReference, error) {
|
||||
return gojsonreference.NewJsonReference("#")
|
||||
}
|
||||
|
||||
func (l *jsonIOLoader) LoaderFactory() JSONLoaderFactory {
|
||||
return &DefaultJSONLoaderFactory{}
|
||||
}
|
||||
|
||||
// JSON raw loader
|
||||
// In case the JSON is already marshalled to any use this loader
|
||||
// This is used for testing as otherwise there is no guarantee the JSON is marshalled
|
||||
// "properly" by using https://golang.org/pkg/encoding/json/#Decoder.UseNumber
|
||||
type jsonRawLoader struct {
|
||||
source any
|
||||
}
|
||||
|
||||
// NewRawLoader creates a new JSON raw loader for the given source
|
||||
func NewRawLoader(source any) JSONLoader {
|
||||
return &jsonRawLoader{source: source}
|
||||
}
|
||||
func (l *jsonRawLoader) JSONSource() any {
|
||||
return l.source
|
||||
}
|
||||
func (l *jsonRawLoader) LoadJSON() (any, error) {
|
||||
return l.source, nil
|
||||
}
|
||||
func (l *jsonRawLoader) JSONReference() (gojsonreference.JsonReference, error) {
|
||||
return gojsonreference.NewJsonReference("#")
|
||||
}
|
||||
func (l *jsonRawLoader) LoaderFactory() JSONLoaderFactory {
|
||||
return &DefaultJSONLoaderFactory{}
|
||||
}
|
||||
|
||||
func decodeJSONUsingNumber(r io.Reader) (any, error) {
|
||||
|
||||
var document any
|
||||
|
||||
decoder := json.NewDecoder(r)
|
||||
decoder.UseNumber()
|
||||
|
||||
err := decoder.Decode(&document)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return document, nil
|
||||
|
||||
}
|
||||
+472
@@ -0,0 +1,472 @@
|
||||
// Copyright 2015 xeipuuv ( https://github.com/xeipuuv )
|
||||
//
|
||||
// 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.
|
||||
|
||||
// author xeipuuv
|
||||
// author-github https://github.com/xeipuuv
|
||||
// author-mail xeipuuv@gmail.com
|
||||
//
|
||||
// repository-name gojsonschema
|
||||
// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language.
|
||||
//
|
||||
// description Contains const string and messages.
|
||||
//
|
||||
// created 01-01-2015
|
||||
|
||||
package gojsonschema
|
||||
|
||||
type (
|
||||
// locale is an interface for defining custom error strings
|
||||
locale interface {
|
||||
|
||||
// False returns a format-string for "false" schema validation errors
|
||||
False() string
|
||||
|
||||
// Required returns a format-string for "required" schema validation errors
|
||||
Required() string
|
||||
|
||||
// InvalidType returns a format-string for "invalid type" schema validation errors
|
||||
InvalidType() string
|
||||
|
||||
// NumberAnyOf returns a format-string for "anyOf" schema validation errors
|
||||
NumberAnyOf() string
|
||||
|
||||
// NumberOneOf returns a format-string for "oneOf" schema validation errors
|
||||
NumberOneOf() string
|
||||
|
||||
// NumberAllOf returns a format-string for "allOf" schema validation errors
|
||||
NumberAllOf() string
|
||||
|
||||
// NumberNot returns a format-string to format a NumberNotError
|
||||
NumberNot() string
|
||||
|
||||
// MissingDependency returns a format-string for "missing dependency" schema validation errors
|
||||
MissingDependency() string
|
||||
|
||||
// Internal returns a format-string for internal errors
|
||||
Internal() string
|
||||
|
||||
// Const returns a format-string to format a ConstError
|
||||
Const() string
|
||||
|
||||
// Enum returns a format-string to format an EnumError
|
||||
Enum() string
|
||||
|
||||
// ArrayNotEnoughItems returns a format-string to format an error for arrays having not enough items to match positional list of schema
|
||||
ArrayNotEnoughItems() string
|
||||
|
||||
// ArrayNoAdditionalItems returns a format-string to format an ArrayNoAdditionalItemsError
|
||||
ArrayNoAdditionalItems() string
|
||||
|
||||
// ArrayMinItems returns a format-string to format an ArrayMinItemsError
|
||||
ArrayMinItems() string
|
||||
|
||||
// ArrayMaxItems returns a format-string to format an ArrayMaxItemsError
|
||||
ArrayMaxItems() string
|
||||
|
||||
// Unique returns a format-string to format an ItemsMustBeUniqueError
|
||||
Unique() string
|
||||
|
||||
// ArrayContains returns a format-string to format an ArrayContainsError
|
||||
ArrayContains() string
|
||||
|
||||
// ArrayMinProperties returns a format-string to format an ArrayMinPropertiesError
|
||||
ArrayMinProperties() string
|
||||
|
||||
// ArrayMaxProperties returns a format-string to format an ArrayMaxPropertiesError
|
||||
ArrayMaxProperties() string
|
||||
|
||||
// AdditionalPropertyNotAllowed returns a format-string to format an AdditionalPropertyNotAllowedError
|
||||
AdditionalPropertyNotAllowed() string
|
||||
|
||||
// InvalidPropertyPattern returns a format-string to format an InvalidPropertyPatternError
|
||||
InvalidPropertyPattern() string
|
||||
|
||||
// InvalidPropertyName returns a format-string to format an InvalidPropertyNameError
|
||||
InvalidPropertyName() string
|
||||
|
||||
// StringGTE returns a format-string to format an StringLengthGTEError
|
||||
StringGTE() string
|
||||
|
||||
// StringLTE returns a format-string to format an StringLengthLTEError
|
||||
StringLTE() string
|
||||
|
||||
// DoesNotMatchPattern returns a format-string to format an DoesNotMatchPatternError
|
||||
DoesNotMatchPattern() string
|
||||
|
||||
// DoesNotMatchFormat returns a format-string to format an DoesNotMatchFormatError
|
||||
DoesNotMatchFormat() string
|
||||
|
||||
// MultipleOf returns a format-string to format an MultipleOfError
|
||||
MultipleOf() string
|
||||
|
||||
// NumberGTE returns a format-string to format an NumberGTEError
|
||||
NumberGTE() string
|
||||
|
||||
// NumberGT returns a format-string to format an NumberGTError
|
||||
NumberGT() string
|
||||
|
||||
// NumberLTE returns a format-string to format an NumberLTEError
|
||||
NumberLTE() string
|
||||
|
||||
// NumberLT returns a format-string to format an NumberLTError
|
||||
NumberLT() string
|
||||
|
||||
// Schema validations
|
||||
|
||||
// RegexPattern returns a format-string to format a regex-pattern error
|
||||
RegexPattern() string
|
||||
|
||||
// GreaterThanZero returns a format-string to format an error where a number must be greater than zero
|
||||
GreaterThanZero() string
|
||||
|
||||
// MustBeOfA returns a format-string to format an error where a value is of the wrong type
|
||||
MustBeOfA() string
|
||||
|
||||
// MustBeOfAn returns a format-string to format an error where a value is of the wrong type
|
||||
MustBeOfAn() string
|
||||
|
||||
// CannotBeUsedWithout returns a format-string to format a "cannot be used without" error
|
||||
CannotBeUsedWithout() string
|
||||
|
||||
// CannotBeGT returns a format-string to format an error where a value are greater than allowed
|
||||
CannotBeGT() string
|
||||
|
||||
// MustBeOfType returns a format-string to format an error where a value does not match the required type
|
||||
MustBeOfType() string
|
||||
|
||||
// MustBeValidRegex returns a format-string to format an error where a regex is invalid
|
||||
MustBeValidRegex() string
|
||||
|
||||
// MustBeValidFormat returns a format-string to format an error where a value does not match the expected format
|
||||
MustBeValidFormat() string
|
||||
|
||||
// MustBeGTEZero returns a format-string to format an error where a value must be greater or equal than 0
|
||||
MustBeGTEZero() string
|
||||
|
||||
// KeyCannotBeGreaterThan returns a format-string to format an error where a key is greater than the maximum allowed
|
||||
KeyCannotBeGreaterThan() string
|
||||
|
||||
// KeyItemsMustBeOfType returns a format-string to format an error where a key is of the wrong type
|
||||
KeyItemsMustBeOfType() string
|
||||
|
||||
// KeyItemsMustBeUnique returns a format-string to format an error where keys are not unique
|
||||
KeyItemsMustBeUnique() string
|
||||
|
||||
// ReferenceMustBeCanonical returns a format-string to format a "reference must be canonical" error
|
||||
ReferenceMustBeCanonical() string
|
||||
|
||||
// NotAValidType returns a format-string to format an invalid type error
|
||||
NotAValidType() string
|
||||
|
||||
// Duplicated returns a format-string to format an error where types are duplicated
|
||||
Duplicated() string
|
||||
|
||||
// HTTPBadStatus returns a format-string for errors when loading a schema using HTTP
|
||||
HTTPBadStatus() string
|
||||
|
||||
// ParseError returns a format-string for JSON parsing errors
|
||||
ParseError() string
|
||||
|
||||
// ConditionThen returns a format-string for ConditionThenError errors
|
||||
ConditionThen() string
|
||||
|
||||
// ConditionElse returns a format-string for ConditionElseError errors
|
||||
ConditionElse() string
|
||||
|
||||
// ErrorFormat returns a format string for errors
|
||||
ErrorFormat() string
|
||||
}
|
||||
|
||||
// DefaultLocale is the default locale for this package
|
||||
DefaultLocale struct{}
|
||||
)
|
||||
|
||||
// False returns a format-string for "false" schema validation errors
|
||||
func (l DefaultLocale) False() string {
|
||||
return "False always fails validation"
|
||||
}
|
||||
|
||||
// Required returns a format-string for "required" schema validation errors
|
||||
func (l DefaultLocale) Required() string {
|
||||
return `{{.property}} is required`
|
||||
}
|
||||
|
||||
// InvalidType returns a format-string for "invalid type" schema validation errors
|
||||
func (l DefaultLocale) InvalidType() string {
|
||||
return `Invalid type. Expected: {{.expected}}, given: {{.given}}`
|
||||
}
|
||||
|
||||
// NumberAnyOf returns a format-string for "anyOf" schema validation errors
|
||||
func (l DefaultLocale) NumberAnyOf() string {
|
||||
return `Must validate at least one schema (anyOf)`
|
||||
}
|
||||
|
||||
// NumberOneOf returns a format-string for "oneOf" schema validation errors
|
||||
func (l DefaultLocale) NumberOneOf() string {
|
||||
return `Must validate one and only one schema (oneOf)`
|
||||
}
|
||||
|
||||
// NumberAllOf returns a format-string for "allOf" schema validation errors
|
||||
func (l DefaultLocale) NumberAllOf() string {
|
||||
return `Must validate all the schemas (allOf)`
|
||||
}
|
||||
|
||||
// NumberNot returns a format-string to format a NumberNotError
|
||||
func (l DefaultLocale) NumberNot() string {
|
||||
return `Must not validate the schema (not)`
|
||||
}
|
||||
|
||||
// MissingDependency returns a format-string for "missing dependency" schema validation errors
|
||||
func (l DefaultLocale) MissingDependency() string {
|
||||
return `Has a dependency on {{.dependency}}`
|
||||
}
|
||||
|
||||
// Internal returns a format-string for internal errors
|
||||
func (l DefaultLocale) Internal() string {
|
||||
return `Internal Error {{.error}}`
|
||||
}
|
||||
|
||||
// Const returns a format-string to format a ConstError
|
||||
func (l DefaultLocale) Const() string {
|
||||
return `{{.field}} does not match: {{.allowed}}`
|
||||
}
|
||||
|
||||
// Enum returns a format-string to format an EnumError
|
||||
func (l DefaultLocale) Enum() string {
|
||||
return `{{.field}} must be one of the following: {{.allowed}}`
|
||||
}
|
||||
|
||||
// ArrayNoAdditionalItems returns a format-string to format an ArrayNoAdditionalItemsError
|
||||
func (l DefaultLocale) ArrayNoAdditionalItems() string {
|
||||
return `No additional items allowed on array`
|
||||
}
|
||||
|
||||
// ArrayNotEnoughItems returns a format-string to format an error for arrays having not enough items to match positional list of schema
|
||||
func (l DefaultLocale) ArrayNotEnoughItems() string {
|
||||
return `Not enough items on array to match positional list of schema`
|
||||
}
|
||||
|
||||
// ArrayMinItems returns a format-string to format an ArrayMinItemsError
|
||||
func (l DefaultLocale) ArrayMinItems() string {
|
||||
return `Array must have at least {{.min}} items`
|
||||
}
|
||||
|
||||
// ArrayMaxItems returns a format-string to format an ArrayMaxItemsError
|
||||
func (l DefaultLocale) ArrayMaxItems() string {
|
||||
return `Array must have at most {{.max}} items`
|
||||
}
|
||||
|
||||
// Unique returns a format-string to format an ItemsMustBeUniqueError
|
||||
func (l DefaultLocale) Unique() string {
|
||||
return `{{.type}} items[{{.i}},{{.j}}] must be unique`
|
||||
}
|
||||
|
||||
// ArrayContains returns a format-string to format an ArrayContainsError
|
||||
func (l DefaultLocale) ArrayContains() string {
|
||||
return `At least one of the items must match`
|
||||
}
|
||||
|
||||
// ArrayMinProperties returns a format-string to format an ArrayMinPropertiesError
|
||||
func (l DefaultLocale) ArrayMinProperties() string {
|
||||
return `Must have at least {{.min}} properties`
|
||||
}
|
||||
|
||||
// ArrayMaxProperties returns a format-string to format an ArrayMaxPropertiesError
|
||||
func (l DefaultLocale) ArrayMaxProperties() string {
|
||||
return `Must have at most {{.max}} properties`
|
||||
}
|
||||
|
||||
// AdditionalPropertyNotAllowed returns a format-string to format an AdditionalPropertyNotAllowedError
|
||||
func (l DefaultLocale) AdditionalPropertyNotAllowed() string {
|
||||
return `Additional property {{.property}} is not allowed`
|
||||
}
|
||||
|
||||
// InvalidPropertyPattern returns a format-string to format an InvalidPropertyPatternError
|
||||
func (l DefaultLocale) InvalidPropertyPattern() string {
|
||||
return `Property "{{.property}}" does not match pattern {{.pattern}}`
|
||||
}
|
||||
|
||||
// InvalidPropertyName returns a format-string to format an InvalidPropertyNameError
|
||||
func (l DefaultLocale) InvalidPropertyName() string {
|
||||
return `Property name of "{{.property}}" does not match`
|
||||
}
|
||||
|
||||
// StringGTE returns a format-string to format an StringLengthGTEError
|
||||
func (l DefaultLocale) StringGTE() string {
|
||||
return `String length must be greater than or equal to {{.min}}`
|
||||
}
|
||||
|
||||
// StringLTE returns a format-string to format an StringLengthLTEError
|
||||
func (l DefaultLocale) StringLTE() string {
|
||||
return `String length must be less than or equal to {{.max}}`
|
||||
}
|
||||
|
||||
// DoesNotMatchPattern returns a format-string to format an DoesNotMatchPatternError
|
||||
func (l DefaultLocale) DoesNotMatchPattern() string {
|
||||
return `Does not match pattern '{{.pattern}}'`
|
||||
}
|
||||
|
||||
// DoesNotMatchFormat returns a format-string to format an DoesNotMatchFormatError
|
||||
func (l DefaultLocale) DoesNotMatchFormat() string {
|
||||
return `Does not match format '{{.format}}'`
|
||||
}
|
||||
|
||||
// MultipleOf returns a format-string to format an MultipleOfError
|
||||
func (l DefaultLocale) MultipleOf() string {
|
||||
return `Must be a multiple of {{.multiple}}`
|
||||
}
|
||||
|
||||
// NumberGTE returns the format string to format a NumberGTEError
|
||||
func (l DefaultLocale) NumberGTE() string {
|
||||
return `Must be greater than or equal to {{.min}}`
|
||||
}
|
||||
|
||||
// NumberGT returns the format string to format a NumberGTError
|
||||
func (l DefaultLocale) NumberGT() string {
|
||||
return `Must be greater than {{.min}}`
|
||||
}
|
||||
|
||||
// NumberLTE returns the format string to format a NumberLTEError
|
||||
func (l DefaultLocale) NumberLTE() string {
|
||||
return `Must be less than or equal to {{.max}}`
|
||||
}
|
||||
|
||||
// NumberLT returns the format string to format a NumberLTError
|
||||
func (l DefaultLocale) NumberLT() string {
|
||||
return `Must be less than {{.max}}`
|
||||
}
|
||||
|
||||
// Schema validators
|
||||
|
||||
// RegexPattern returns a format-string to format a regex-pattern error
|
||||
func (l DefaultLocale) RegexPattern() string {
|
||||
return `Invalid regex pattern '{{.pattern}}'`
|
||||
}
|
||||
|
||||
// GreaterThanZero returns a format-string to format an error where a number must be greater than zero
|
||||
func (l DefaultLocale) GreaterThanZero() string {
|
||||
return `{{.number}} must be strictly greater than 0`
|
||||
}
|
||||
|
||||
// MustBeOfA returns a format-string to format an error where a value is of the wrong type
|
||||
func (l DefaultLocale) MustBeOfA() string {
|
||||
return `{{.x}} must be of a {{.y}}`
|
||||
}
|
||||
|
||||
// MustBeOfAn returns a format-string to format an error where a value is of the wrong type
|
||||
func (l DefaultLocale) MustBeOfAn() string {
|
||||
return `{{.x}} must be of an {{.y}}`
|
||||
}
|
||||
|
||||
// CannotBeUsedWithout returns a format-string to format a "cannot be used without" error
|
||||
func (l DefaultLocale) CannotBeUsedWithout() string {
|
||||
return `{{.x}} cannot be used without {{.y}}`
|
||||
}
|
||||
|
||||
// CannotBeGT returns a format-string to format an error where a value are greater than allowed
|
||||
func (l DefaultLocale) CannotBeGT() string {
|
||||
return `{{.x}} cannot be greater than {{.y}}`
|
||||
}
|
||||
|
||||
// MustBeOfType returns a format-string to format an error where a value does not match the required type
|
||||
func (l DefaultLocale) MustBeOfType() string {
|
||||
return `{{.key}} must be of type {{.type}}`
|
||||
}
|
||||
|
||||
// MustBeValidRegex returns a format-string to format an error where a regex is invalid
|
||||
func (l DefaultLocale) MustBeValidRegex() string {
|
||||
return `{{.key}} must be a valid regex`
|
||||
}
|
||||
|
||||
// MustBeValidFormat returns a format-string to format an error where a value does not match the expected format
|
||||
func (l DefaultLocale) MustBeValidFormat() string {
|
||||
return `{{.key}} must be a valid format {{.given}}`
|
||||
}
|
||||
|
||||
// MustBeGTEZero returns a format-string to format an error where a value must be greater or equal than 0
|
||||
func (l DefaultLocale) MustBeGTEZero() string {
|
||||
return `{{.key}} must be greater than or equal to 0`
|
||||
}
|
||||
|
||||
// KeyCannotBeGreaterThan returns a format-string to format an error where a value is greater than the maximum allowed
|
||||
func (l DefaultLocale) KeyCannotBeGreaterThan() string {
|
||||
return `{{.key}} cannot be greater than {{.y}}`
|
||||
}
|
||||
|
||||
// KeyItemsMustBeOfType returns a format-string to format an error where a key is of the wrong type
|
||||
func (l DefaultLocale) KeyItemsMustBeOfType() string {
|
||||
return `{{.key}} items must be {{.type}}`
|
||||
}
|
||||
|
||||
// KeyItemsMustBeUnique returns a format-string to format an error where keys are not unique
|
||||
func (l DefaultLocale) KeyItemsMustBeUnique() string {
|
||||
return `{{.key}} items must be unique`
|
||||
}
|
||||
|
||||
// ReferenceMustBeCanonical returns a format-string to format a "reference must be canonical" error
|
||||
func (l DefaultLocale) ReferenceMustBeCanonical() string {
|
||||
return `Reference {{.reference}} must be canonical`
|
||||
}
|
||||
|
||||
// NotAValidType returns a format-string to format an invalid type error
|
||||
func (l DefaultLocale) NotAValidType() string {
|
||||
return `has a primitive type that is NOT VALID -- given: {{.given}} Expected valid values are:{{.expected}}`
|
||||
}
|
||||
|
||||
// Duplicated returns a format-string to format an error where types are duplicated
|
||||
func (l DefaultLocale) Duplicated() string {
|
||||
return `{{.type}} type is duplicated`
|
||||
}
|
||||
|
||||
// HTTPBadStatus returns a format-string for errors when loading a schema using HTTP
|
||||
func (l DefaultLocale) HTTPBadStatus() string {
|
||||
return `Could not read schema from HTTP, response status is {{.status}}`
|
||||
}
|
||||
|
||||
// ErrorFormat returns a format string for errors
|
||||
// Replacement options: field, description, context, value
|
||||
func (l DefaultLocale) ErrorFormat() string {
|
||||
return `{{.field}}: {{.description}}`
|
||||
}
|
||||
|
||||
// ParseError returns a format-string for JSON parsing errors
|
||||
func (l DefaultLocale) ParseError() string {
|
||||
return `Expected: {{.expected}}, given: Invalid JSON`
|
||||
}
|
||||
|
||||
// ConditionThen returns a format-string for ConditionThenError errors
|
||||
// If/Else
|
||||
func (l DefaultLocale) ConditionThen() string {
|
||||
return `Must validate "then" as "if" was valid`
|
||||
}
|
||||
|
||||
// ConditionElse returns a format-string for ConditionElseError errors
|
||||
func (l DefaultLocale) ConditionElse() string {
|
||||
return `Must validate "else" as "if" was not valid`
|
||||
}
|
||||
|
||||
// constants
|
||||
const (
|
||||
StringNumber = "Number"
|
||||
StringArrayOfStrings = "Array Of Strings"
|
||||
StringArrayOfSchemas = "Array Of Schemas"
|
||||
StringSchema = "Valid Schema"
|
||||
StringSchemaOrArrayOfStrings = "Schema Or Array Of Strings"
|
||||
StringProperties = "Properties"
|
||||
StringDependency = "Dependency"
|
||||
StringProperty = "Property"
|
||||
StringUndefined = "Undefined"
|
||||
StringContextRoot = "(Root)"
|
||||
StringRootSchemaProperty = "(Root)"
|
||||
)
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
// Copyright 2015 xeipuuv ( https://github.com/xeipuuv )
|
||||
//
|
||||
// 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.
|
||||
|
||||
// author xeipuuv
|
||||
// author-github https://github.com/xeipuuv
|
||||
// author-mail xeipuuv@gmail.com
|
||||
//
|
||||
// repository-name gojsonschema
|
||||
// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language.
|
||||
//
|
||||
// description Result and ResultError implementations.
|
||||
//
|
||||
// created 01-01-2015
|
||||
|
||||
package gojsonschema
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
// ErrorDetails is a map of details specific to each error.
|
||||
// While the values will vary, every error will contain a "field" value
|
||||
ErrorDetails map[string]any
|
||||
|
||||
// ResultError is the interface that library errors must implement
|
||||
ResultError interface {
|
||||
// Field returns the field name without the root context
|
||||
// i.e. firstName or person.firstName instead of (root).firstName or (root).person.firstName
|
||||
Field() string
|
||||
// SetType sets the error-type
|
||||
SetType(string)
|
||||
// Type returns the error-type
|
||||
Type() string
|
||||
// SetContext sets the JSON-context for the error
|
||||
SetContext(*JSONContext)
|
||||
// Context returns the JSON-context of the error
|
||||
Context() *JSONContext
|
||||
// SetDescription sets a description for the error
|
||||
SetDescription(string)
|
||||
// Description returns the description of the error
|
||||
Description() string
|
||||
// SetDescriptionFormat sets the format for the description in the default text/template format
|
||||
SetDescriptionFormat(string)
|
||||
// DescriptionFormat returns the format for the description in the default text/template format
|
||||
DescriptionFormat() string
|
||||
// SetValue sets the value related to the error
|
||||
SetValue(any)
|
||||
// Value returns the value related to the error
|
||||
Value() any
|
||||
// SetDetails sets the details specific to the error
|
||||
SetDetails(ErrorDetails)
|
||||
// Details returns details about the error
|
||||
Details() ErrorDetails
|
||||
// String returns a string representation of the error
|
||||
String() string
|
||||
}
|
||||
|
||||
// ResultErrorFields holds the fields for each ResultError implementation.
|
||||
// ResultErrorFields implements the ResultError interface, so custom errors
|
||||
// can be defined by just embedding this type
|
||||
ResultErrorFields struct {
|
||||
errorType string // A string with the type of error (i.e. invalid_type)
|
||||
context *JSONContext // Tree like notation of the part that failed the validation. ex (root).a.b ...
|
||||
description string // A human readable error message
|
||||
descriptionFormat string // A format for human readable error message
|
||||
value any // Value given by the JSON file that is the source of the error
|
||||
details ErrorDetails
|
||||
}
|
||||
|
||||
// Result holds the result of a validation
|
||||
Result struct {
|
||||
errors []ResultError
|
||||
// Scores how well the validation matched. Useful in generating
|
||||
// better error messages for anyOf and oneOf.
|
||||
score int
|
||||
}
|
||||
)
|
||||
|
||||
// Field returns the field name without the root context
|
||||
// i.e. firstName or person.firstName instead of (root).firstName or (root).person.firstName
|
||||
func (v *ResultErrorFields) Field() string {
|
||||
return strings.TrimPrefix(v.context.String(), StringRootSchemaProperty+".")
|
||||
}
|
||||
|
||||
// SetType sets the error-type
|
||||
func (v *ResultErrorFields) SetType(errorType string) {
|
||||
v.errorType = errorType
|
||||
}
|
||||
|
||||
// Type returns the error-type
|
||||
func (v *ResultErrorFields) Type() string {
|
||||
return v.errorType
|
||||
}
|
||||
|
||||
// SetContext sets the JSON-context for the error
|
||||
func (v *ResultErrorFields) SetContext(context *JSONContext) {
|
||||
v.context = context
|
||||
}
|
||||
|
||||
// Context returns the JSON-context of the error
|
||||
func (v *ResultErrorFields) Context() *JSONContext {
|
||||
return v.context
|
||||
}
|
||||
|
||||
// SetDescription sets a description for the error
|
||||
func (v *ResultErrorFields) SetDescription(description string) {
|
||||
v.description = description
|
||||
}
|
||||
|
||||
// Description returns the description of the error
|
||||
func (v *ResultErrorFields) Description() string {
|
||||
return v.description
|
||||
}
|
||||
|
||||
// SetDescriptionFormat sets the format for the description in the default text/template format
|
||||
func (v *ResultErrorFields) SetDescriptionFormat(descriptionFormat string) {
|
||||
v.descriptionFormat = descriptionFormat
|
||||
}
|
||||
|
||||
// DescriptionFormat returns the format for the description in the default text/template format
|
||||
func (v *ResultErrorFields) DescriptionFormat() string {
|
||||
return v.descriptionFormat
|
||||
}
|
||||
|
||||
// SetValue sets the value related to the error
|
||||
func (v *ResultErrorFields) SetValue(value any) {
|
||||
v.value = value
|
||||
}
|
||||
|
||||
// Value returns the value related to the error
|
||||
func (v *ResultErrorFields) Value() any {
|
||||
return v.value
|
||||
}
|
||||
|
||||
// SetDetails sets the details specific to the error
|
||||
func (v *ResultErrorFields) SetDetails(details ErrorDetails) {
|
||||
v.details = details
|
||||
}
|
||||
|
||||
// Details returns details about the error
|
||||
func (v *ResultErrorFields) Details() ErrorDetails {
|
||||
return v.details
|
||||
}
|
||||
|
||||
// String returns a string representation of the error
|
||||
func (v ResultErrorFields) String() string {
|
||||
// as a fallback, the value is displayed go style
|
||||
valueString := fmt.Sprintf("%v", v.value)
|
||||
|
||||
// marshal the go value value to json
|
||||
if v.value == nil {
|
||||
valueString = TypeNull
|
||||
} else {
|
||||
if vs, err := marshalToJSONString(v.value); err == nil {
|
||||
if vs == nil {
|
||||
valueString = TypeNull
|
||||
} else {
|
||||
valueString = *vs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return formatErrorDescription(Locale.ErrorFormat(), ErrorDetails{
|
||||
"context": v.context.String(),
|
||||
"description": v.description,
|
||||
"value": valueString,
|
||||
"field": v.Field(),
|
||||
})
|
||||
}
|
||||
|
||||
// Valid indicates if no errors were found
|
||||
func (v *Result) Valid() bool {
|
||||
return len(v.errors) == 0
|
||||
}
|
||||
|
||||
// Errors returns the errors that were found
|
||||
func (v *Result) Errors() []ResultError {
|
||||
return v.errors
|
||||
}
|
||||
|
||||
// AddError appends a fully filled error to the error set
|
||||
// SetDescription() will be called with the result of the parsed err.DescriptionFormat()
|
||||
func (v *Result) AddError(err ResultError, details ErrorDetails) {
|
||||
if _, exists := details["context"]; !exists && err.Context() != nil {
|
||||
details["context"] = err.Context().String()
|
||||
}
|
||||
|
||||
err.SetDescription(formatErrorDescription(err.DescriptionFormat(), details))
|
||||
|
||||
v.errors = append(v.errors, err)
|
||||
}
|
||||
|
||||
func (v *Result) addInternalError(err ResultError, context *JSONContext, value any, details ErrorDetails) {
|
||||
newError(err, context, value, Locale, details)
|
||||
v.errors = append(v.errors, err)
|
||||
v.score -= 2 // results in a net -1 when added to the +1 we get at the end of the validation function
|
||||
}
|
||||
|
||||
// Used to copy errors from a sub-schema to the main one
|
||||
func (v *Result) mergeErrors(otherResult *Result) {
|
||||
v.errors = append(v.errors, otherResult.Errors()...)
|
||||
v.score += otherResult.score
|
||||
}
|
||||
|
||||
func (v *Result) incrementScore() {
|
||||
v.score++
|
||||
}
|
||||
+957
@@ -0,0 +1,957 @@
|
||||
// Copyright 2015 xeipuuv ( https://github.com/xeipuuv )
|
||||
//
|
||||
// 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.
|
||||
|
||||
// author xeipuuv
|
||||
// author-github https://github.com/xeipuuv
|
||||
// author-mail xeipuuv@gmail.com
|
||||
//
|
||||
// repository-name gojsonschema
|
||||
// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language.
|
||||
//
|
||||
// description Defines Schema, the main entry to every SubSchema.
|
||||
// Contains the parsing logic and error checking.
|
||||
//
|
||||
// created 26-02-2013
|
||||
|
||||
package gojsonschema
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/big"
|
||||
"regexp"
|
||||
"text/template"
|
||||
|
||||
"github.com/xeipuuv/gojsonreference"
|
||||
)
|
||||
|
||||
var (
|
||||
// Locale is the default locale to use
|
||||
// Library users can overwrite with their own implementation
|
||||
Locale locale = DefaultLocale{}
|
||||
|
||||
// ErrorTemplateFuncs allows you to define custom template funcs for use in localization.
|
||||
ErrorTemplateFuncs template.FuncMap
|
||||
)
|
||||
|
||||
// NewSchema instances a schema using the given JSONLoader
|
||||
func NewSchema(l JSONLoader) (*Schema, error) {
|
||||
return NewSchemaLoader().Compile(l)
|
||||
}
|
||||
|
||||
// Schema holds a schema
|
||||
type Schema struct {
|
||||
DocumentReference gojsonreference.JsonReference
|
||||
RootSchema *SubSchema
|
||||
Pool *schemaPool
|
||||
ReferencePool *schemaReferencePool
|
||||
}
|
||||
|
||||
func (d *Schema) parse(document any, draft Draft) error {
|
||||
d.RootSchema = &SubSchema{Property: StringRootSchemaProperty, Draft: &draft}
|
||||
return d.parseSchema(document, d.RootSchema)
|
||||
}
|
||||
|
||||
// SetRootSchemaName sets the root-schema name
|
||||
func (d *Schema) SetRootSchemaName(name string) {
|
||||
d.RootSchema.Property = name
|
||||
}
|
||||
|
||||
// Parses a SubSchema
|
||||
//
|
||||
// Pretty long function ( sorry :) )... but pretty straight forward, repetitive and boring
|
||||
// Not much magic involved here, most of the job is to validate the key names and their values,
|
||||
// then the values are copied into SubSchema struct
|
||||
func (d *Schema) parseSchema(documentNode any, currentSchema *SubSchema) error {
|
||||
|
||||
if currentSchema.Draft == nil {
|
||||
if currentSchema.Parent == nil {
|
||||
return errors.New("Draft not set")
|
||||
}
|
||||
currentSchema.Draft = currentSchema.Parent.Draft
|
||||
}
|
||||
|
||||
// As of draft 6 "true" is equivalent to an empty schema "{}" and false equals "{"not":{}}"
|
||||
if *currentSchema.Draft >= Draft6 {
|
||||
if b, isBool := documentNode.(bool); isBool {
|
||||
currentSchema.pass = &b
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
m, isMap := documentNode.(map[string]any)
|
||||
if !isMap {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.ParseError(),
|
||||
ErrorDetails{
|
||||
"expected": StringSchema,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
if currentSchema.Parent == nil {
|
||||
currentSchema.Ref = &d.DocumentReference
|
||||
currentSchema.ID = &d.DocumentReference
|
||||
}
|
||||
|
||||
if currentSchema.ID == nil && currentSchema.Parent != nil {
|
||||
currentSchema.ID = currentSchema.Parent.ID
|
||||
}
|
||||
|
||||
// In draft 6 the id keyword was renamed to $id
|
||||
// Hybrid mode uses the old id by default
|
||||
var keyID string
|
||||
|
||||
switch *currentSchema.Draft {
|
||||
case Draft4:
|
||||
keyID = KeyID
|
||||
case Hybrid:
|
||||
keyID = KeyIDNew
|
||||
if _, found := m[KeyID]; found {
|
||||
keyID = KeyID
|
||||
}
|
||||
default:
|
||||
keyID = KeyIDNew
|
||||
}
|
||||
|
||||
if id, err := getString(m, keyID); err != nil {
|
||||
return err
|
||||
} else if id != nil {
|
||||
jsonReference, err := gojsonreference.NewJsonReference(*id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if currentSchema == d.RootSchema {
|
||||
currentSchema.ID = &jsonReference
|
||||
} else {
|
||||
ref, err := currentSchema.Parent.ID.Inherits(jsonReference)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentSchema.ID = ref
|
||||
}
|
||||
}
|
||||
|
||||
// definitions
|
||||
if v, ok := m[KeyDefinitions]; ok {
|
||||
switch mt := v.(type) {
|
||||
case map[string]any:
|
||||
for _, dv := range mt {
|
||||
switch dv.(type) {
|
||||
case bool, map[string]any:
|
||||
newSchema := &SubSchema{Property: KeyDefinitions, Parent: currentSchema}
|
||||
err := d.parseSchema(dv, newSchema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return invalidType(StringArrayOfSchemas, KeyDefinitions)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return invalidType(StringArrayOfSchemas, KeyDefinitions)
|
||||
}
|
||||
}
|
||||
|
||||
// title
|
||||
var err error
|
||||
currentSchema.title, err = getString(m, KeyTitle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// description
|
||||
currentSchema.description, err = getString(m, KeyDescription)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// $ref
|
||||
if ref, err := getString(m, KeyRef); err != nil {
|
||||
return err
|
||||
} else if ref != nil {
|
||||
jsonReference, err := gojsonreference.NewJsonReference(*ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentSchema.Ref = &jsonReference
|
||||
|
||||
if sch, ok := d.ReferencePool.Get(currentSchema.Ref.String()); ok {
|
||||
currentSchema.RefSchema = sch
|
||||
} else {
|
||||
return d.parseReference(documentNode, currentSchema)
|
||||
}
|
||||
}
|
||||
|
||||
// type
|
||||
if typ, found := m[KeyType]; found {
|
||||
switch t := typ.(type) {
|
||||
case string:
|
||||
err := currentSchema.Types.Add(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case []any:
|
||||
for _, typeInArray := range t {
|
||||
s, isString := typeInArray.(string)
|
||||
if !isString {
|
||||
return invalidType(KeyType, TypeString+"/"+StringArrayOfStrings)
|
||||
}
|
||||
if err := currentSchema.Types.Add(s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
return invalidType(KeyType, TypeString+"/"+StringArrayOfStrings)
|
||||
}
|
||||
}
|
||||
|
||||
// properties
|
||||
if properties, found := m[KeyProperties]; found {
|
||||
err := d.parseProperties(properties, currentSchema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// additionalProperties
|
||||
if additionalProperties, found := m[KeyAdditionalProperties]; found {
|
||||
switch v := additionalProperties.(type) {
|
||||
case bool:
|
||||
currentSchema.additionalProperties = v
|
||||
case map[string]any:
|
||||
newSchema := &SubSchema{Property: KeyAdditionalProperties, Parent: currentSchema, Ref: currentSchema.Ref}
|
||||
currentSchema.additionalProperties = newSchema
|
||||
err := d.parseSchema(v, newSchema)
|
||||
if err != nil {
|
||||
return errors.New(err.Error())
|
||||
}
|
||||
default:
|
||||
return invalidType(TypeBoolean+"/"+StringSchema, KeyAdditionalProperties)
|
||||
}
|
||||
}
|
||||
|
||||
// patternProperties
|
||||
if patternProperties, err := getMap(m, KeyPatternProperties); err != nil {
|
||||
return err
|
||||
} else if patternProperties != nil {
|
||||
if len(patternProperties) > 0 {
|
||||
currentSchema.patternProperties = make(map[string]*SubSchema)
|
||||
for k, v := range patternProperties {
|
||||
_, err := regexp.MatchString(k, "")
|
||||
if err != nil {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.RegexPattern(),
|
||||
ErrorDetails{"pattern": k},
|
||||
))
|
||||
}
|
||||
newSchema := &SubSchema{Property: k, Parent: currentSchema, Ref: currentSchema.Ref}
|
||||
err = d.parseSchema(v, newSchema)
|
||||
if err != nil {
|
||||
return errors.New(err.Error())
|
||||
}
|
||||
currentSchema.patternProperties[k] = newSchema
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// propertyNames
|
||||
if propertyNames, found := m[KeyPropertyNames]; found && *currentSchema.Draft >= Draft6 {
|
||||
switch propertyNames.(type) {
|
||||
case bool, map[string]any:
|
||||
newSchema := &SubSchema{Property: KeyPropertyNames, Parent: currentSchema, Ref: currentSchema.Ref}
|
||||
currentSchema.propertyNames = newSchema
|
||||
err := d.parseSchema(propertyNames, newSchema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.InvalidType(),
|
||||
ErrorDetails{
|
||||
"expected": StringSchema,
|
||||
"given": KeyPatternProperties,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// dependencies
|
||||
if dependencies, found := m[KeyDependencies]; found {
|
||||
err := d.parseDependencies(dependencies, currentSchema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// items
|
||||
if items, found := m[KeyItems]; found {
|
||||
switch i := items.(type) {
|
||||
case []any:
|
||||
for _, itemElement := range i {
|
||||
switch itemElement.(type) {
|
||||
case map[string]any, bool:
|
||||
newSchema := &SubSchema{Parent: currentSchema, Property: KeyItems}
|
||||
newSchema.Ref = currentSchema.Ref
|
||||
currentSchema.ItemsChildren = append(currentSchema.ItemsChildren, newSchema)
|
||||
err := d.parseSchema(itemElement, newSchema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return invalidType(StringSchema+"/"+StringArrayOfSchemas, KeyItems)
|
||||
}
|
||||
currentSchema.ItemsChildrenIsSingleSchema = false
|
||||
}
|
||||
case map[string]any, bool:
|
||||
newSchema := &SubSchema{Parent: currentSchema, Property: KeyItems}
|
||||
newSchema.Ref = currentSchema.Ref
|
||||
currentSchema.ItemsChildren = append(currentSchema.ItemsChildren, newSchema)
|
||||
err := d.parseSchema(items, newSchema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentSchema.ItemsChildrenIsSingleSchema = true
|
||||
default:
|
||||
return invalidType(StringSchema+"/"+StringArrayOfSchemas, KeyItems)
|
||||
}
|
||||
}
|
||||
|
||||
// additionalItems
|
||||
if additionalItems, found := m[KeyAdditionalItems]; found {
|
||||
switch i := additionalItems.(type) {
|
||||
case bool:
|
||||
currentSchema.additionalItems = i
|
||||
case map[string]any:
|
||||
newSchema := &SubSchema{Property: KeyAdditionalItems, Parent: currentSchema, Ref: currentSchema.Ref}
|
||||
currentSchema.additionalItems = newSchema
|
||||
err := d.parseSchema(additionalItems, newSchema)
|
||||
if err != nil {
|
||||
return errors.New(err.Error())
|
||||
}
|
||||
default:
|
||||
return invalidType(TypeBoolean+"/"+StringSchema, KeyAdditionalItems)
|
||||
}
|
||||
}
|
||||
|
||||
// validation : number / integer
|
||||
if multipleOf, found := m[KeyMultipleOf]; found {
|
||||
multipleOfValue := mustBeNumber(multipleOf)
|
||||
if multipleOfValue == nil {
|
||||
return invalidType(StringNumber, KeyMultipleOf)
|
||||
}
|
||||
if multipleOfValue.Cmp(big.NewRat(0, 1)) <= 0 {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.GreaterThanZero(),
|
||||
ErrorDetails{"number": KeyMultipleOf},
|
||||
))
|
||||
}
|
||||
currentSchema.multipleOf = multipleOfValue
|
||||
}
|
||||
|
||||
if minimum, found := m[KeyMinimum]; found {
|
||||
minimumValue := mustBeNumber(minimum)
|
||||
if minimumValue == nil {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.MustBeOfA(),
|
||||
ErrorDetails{"x": KeyMinimum, "y": StringNumber},
|
||||
))
|
||||
}
|
||||
currentSchema.minimum = minimumValue
|
||||
}
|
||||
|
||||
if exclusiveMinimum, found := m[KeyExclusiveMinimum]; found {
|
||||
switch *currentSchema.Draft {
|
||||
case Draft4:
|
||||
boolExclusiveMinimum, isBool := exclusiveMinimum.(bool)
|
||||
if !isBool {
|
||||
return invalidType(TypeBoolean, KeyExclusiveMinimum)
|
||||
}
|
||||
if currentSchema.minimum == nil {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.CannotBeUsedWithout(),
|
||||
ErrorDetails{"x": KeyExclusiveMinimum, "y": KeyMinimum},
|
||||
))
|
||||
}
|
||||
if boolExclusiveMinimum {
|
||||
currentSchema.exclusiveMinimum = currentSchema.minimum
|
||||
currentSchema.minimum = nil
|
||||
}
|
||||
case Hybrid:
|
||||
switch b := exclusiveMinimum.(type) {
|
||||
case bool:
|
||||
if currentSchema.minimum == nil {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.CannotBeUsedWithout(),
|
||||
ErrorDetails{"x": KeyExclusiveMinimum, "y": KeyMinimum},
|
||||
))
|
||||
}
|
||||
if b {
|
||||
currentSchema.exclusiveMinimum = currentSchema.minimum
|
||||
currentSchema.minimum = nil
|
||||
}
|
||||
case json.Number:
|
||||
currentSchema.exclusiveMinimum = mustBeNumber(m[KeyExclusiveMinimum])
|
||||
default:
|
||||
return invalidType(TypeBoolean+"/"+TypeNumber, KeyExclusiveMinimum)
|
||||
}
|
||||
default:
|
||||
if isJSONNumber(exclusiveMinimum) {
|
||||
currentSchema.exclusiveMinimum = mustBeNumber(exclusiveMinimum)
|
||||
} else {
|
||||
return invalidType(TypeNumber, KeyExclusiveMinimum)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if maximum, found := m[KeyMaximum]; found {
|
||||
maximumValue := mustBeNumber(maximum)
|
||||
if maximumValue == nil {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.MustBeOfA(),
|
||||
ErrorDetails{"x": KeyMaximum, "y": StringNumber},
|
||||
))
|
||||
}
|
||||
currentSchema.maximum = maximumValue
|
||||
}
|
||||
|
||||
if exclusiveMaximum, found := m[KeyExclusiveMaximum]; found {
|
||||
switch *currentSchema.Draft {
|
||||
case Draft4:
|
||||
boolExclusiveMaximum, isBool := exclusiveMaximum.(bool)
|
||||
if !isBool {
|
||||
return invalidType(TypeBoolean, KeyExclusiveMaximum)
|
||||
}
|
||||
if currentSchema.maximum == nil {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.CannotBeUsedWithout(),
|
||||
ErrorDetails{"x": KeyExclusiveMaximum, "y": KeyMaximum},
|
||||
))
|
||||
}
|
||||
if boolExclusiveMaximum {
|
||||
currentSchema.exclusiveMaximum = currentSchema.maximum
|
||||
currentSchema.maximum = nil
|
||||
}
|
||||
case Hybrid:
|
||||
switch b := exclusiveMaximum.(type) {
|
||||
case bool:
|
||||
if currentSchema.maximum == nil {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.CannotBeUsedWithout(),
|
||||
ErrorDetails{"x": KeyExclusiveMaximum, "y": KeyMaximum},
|
||||
))
|
||||
}
|
||||
if b {
|
||||
currentSchema.exclusiveMaximum = currentSchema.maximum
|
||||
currentSchema.maximum = nil
|
||||
}
|
||||
case json.Number:
|
||||
currentSchema.exclusiveMaximum = mustBeNumber(exclusiveMaximum)
|
||||
default:
|
||||
return invalidType(TypeBoolean+"/"+TypeNumber, KeyExclusiveMaximum)
|
||||
}
|
||||
default:
|
||||
if isJSONNumber(exclusiveMaximum) {
|
||||
currentSchema.exclusiveMaximum = mustBeNumber(exclusiveMaximum)
|
||||
} else {
|
||||
return invalidType(TypeNumber, KeyExclusiveMaximum)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// validation : string
|
||||
|
||||
if minLength, found := m[KeyMinLength]; found {
|
||||
minLengthIntegerValue := mustBeInteger(minLength)
|
||||
if minLengthIntegerValue == nil {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.MustBeOfAn(),
|
||||
ErrorDetails{"x": KeyMinLength, "y": TypeInteger},
|
||||
))
|
||||
}
|
||||
if *minLengthIntegerValue < 0 {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.MustBeGTEZero(),
|
||||
ErrorDetails{"key": KeyMinLength},
|
||||
))
|
||||
}
|
||||
currentSchema.minLength = minLengthIntegerValue
|
||||
}
|
||||
|
||||
if maxLength, found := m[KeyMaxLength]; found {
|
||||
maxLengthIntegerValue := mustBeInteger(maxLength)
|
||||
if maxLengthIntegerValue == nil {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.MustBeOfAn(),
|
||||
ErrorDetails{"x": KeyMaxLength, "y": TypeInteger},
|
||||
))
|
||||
}
|
||||
if *maxLengthIntegerValue < 0 {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.MustBeGTEZero(),
|
||||
ErrorDetails{"key": KeyMaxLength},
|
||||
))
|
||||
}
|
||||
currentSchema.maxLength = maxLengthIntegerValue
|
||||
}
|
||||
|
||||
if currentSchema.minLength != nil && currentSchema.maxLength != nil {
|
||||
if *currentSchema.minLength > *currentSchema.maxLength {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.CannotBeGT(),
|
||||
ErrorDetails{"x": KeyMinLength, "y": KeyMaxLength},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Regex compilation step removed as we don't use "pattern" attribute for
|
||||
// type checking, and this would cause schemas to fail if they included patterns
|
||||
// that were valid ECMA regex dialect but not known to Go (i.e. the regexp.Compile
|
||||
// function), such as patterns with negative lookahead
|
||||
if _, err := getString(m, KeyPattern); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if format, err := getString(m, KeyFormat); err != nil {
|
||||
return err
|
||||
} else if format != nil {
|
||||
currentSchema.format = *format
|
||||
}
|
||||
|
||||
// validation : object
|
||||
|
||||
if minProperties, found := m[KeyMinProperties]; found {
|
||||
minPropertiesIntegerValue := mustBeInteger(minProperties)
|
||||
if minPropertiesIntegerValue == nil {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.MustBeOfAn(),
|
||||
ErrorDetails{"x": KeyMinProperties, "y": TypeInteger},
|
||||
))
|
||||
}
|
||||
if *minPropertiesIntegerValue < 0 {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.MustBeGTEZero(),
|
||||
ErrorDetails{"key": KeyMinProperties},
|
||||
))
|
||||
}
|
||||
currentSchema.minProperties = minPropertiesIntegerValue
|
||||
}
|
||||
|
||||
if maxProperties, found := m[KeyMaxProperties]; found {
|
||||
maxPropertiesIntegerValue := mustBeInteger(maxProperties)
|
||||
if maxPropertiesIntegerValue == nil {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.MustBeOfAn(),
|
||||
ErrorDetails{"x": KeyMaxProperties, "y": TypeInteger},
|
||||
))
|
||||
}
|
||||
if *maxPropertiesIntegerValue < 0 {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.MustBeGTEZero(),
|
||||
ErrorDetails{"key": KeyMaxProperties},
|
||||
))
|
||||
}
|
||||
currentSchema.maxProperties = maxPropertiesIntegerValue
|
||||
}
|
||||
|
||||
if currentSchema.minProperties != nil && currentSchema.maxProperties != nil {
|
||||
if *currentSchema.minProperties > *currentSchema.maxProperties {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.KeyCannotBeGreaterThan(),
|
||||
ErrorDetails{"key": KeyMinProperties, "y": KeyMaxProperties},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
required, err := getSlice(m, KeyRequired)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, requiredValue := range required {
|
||||
s, isString := requiredValue.(string)
|
||||
if !isString {
|
||||
return invalidType(TypeString, KeyRequired)
|
||||
} else if isStringInSlice(currentSchema.required, s) {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.KeyItemsMustBeUnique(),
|
||||
ErrorDetails{"key": KeyRequired},
|
||||
))
|
||||
}
|
||||
currentSchema.required = append(currentSchema.required, s)
|
||||
}
|
||||
|
||||
// validation : array
|
||||
|
||||
if minItems, found := m[KeyMinItems]; found {
|
||||
minItemsIntegerValue := mustBeInteger(minItems)
|
||||
if minItemsIntegerValue == nil {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.MustBeOfAn(),
|
||||
ErrorDetails{"x": KeyMinItems, "y": TypeInteger},
|
||||
))
|
||||
}
|
||||
if *minItemsIntegerValue < 0 {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.MustBeGTEZero(),
|
||||
ErrorDetails{"key": KeyMinItems},
|
||||
))
|
||||
}
|
||||
currentSchema.minItems = minItemsIntegerValue
|
||||
}
|
||||
|
||||
if maxItems, found := m[KeyMaxItems]; found {
|
||||
maxItemsIntegerValue := mustBeInteger(maxItems)
|
||||
if maxItemsIntegerValue == nil {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.MustBeOfAn(),
|
||||
ErrorDetails{"x": KeyMaxItems, "y": TypeInteger},
|
||||
))
|
||||
}
|
||||
if *maxItemsIntegerValue < 0 {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.MustBeGTEZero(),
|
||||
ErrorDetails{"key": KeyMaxItems},
|
||||
))
|
||||
}
|
||||
currentSchema.maxItems = maxItemsIntegerValue
|
||||
}
|
||||
|
||||
if uniqueItems, found := m[KeyUniqueItems]; found {
|
||||
bUniqueItems, isBool := uniqueItems.(bool)
|
||||
if !isBool {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.MustBeOfA(),
|
||||
ErrorDetails{"x": KeyUniqueItems, "y": TypeBoolean},
|
||||
))
|
||||
}
|
||||
currentSchema.uniqueItems = bUniqueItems
|
||||
}
|
||||
|
||||
if contains, found := m[KeyContains]; found && *currentSchema.Draft >= Draft6 {
|
||||
newSchema := &SubSchema{Property: KeyContains, Parent: currentSchema, Ref: currentSchema.Ref}
|
||||
currentSchema.contains = newSchema
|
||||
err := d.parseSchema(contains, newSchema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// validation : all
|
||||
if vConst, found := m[KeyConst]; found && *currentSchema.Draft >= Draft6 {
|
||||
is, err := marshalWithoutNumber(vConst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentSchema._const = is
|
||||
}
|
||||
|
||||
enum, err := getSlice(m, KeyEnum)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range enum {
|
||||
is, err := marshalWithoutNumber(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isStringInSlice(currentSchema.enum, *is) {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.KeyItemsMustBeUnique(),
|
||||
ErrorDetails{"key": KeyEnum},
|
||||
))
|
||||
}
|
||||
currentSchema.enum = append(currentSchema.enum, *is)
|
||||
}
|
||||
|
||||
// validation : SubSchema
|
||||
oneOf, err := getSlice(m, KeyOneOf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range oneOf {
|
||||
newSchema := &SubSchema{Property: KeyOneOf, Parent: currentSchema, Ref: currentSchema.Ref}
|
||||
currentSchema.oneOf = append(currentSchema.oneOf, newSchema)
|
||||
err := d.parseSchema(v, newSchema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
anyOf, err := getSlice(m, KeyAnyOf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range anyOf {
|
||||
newSchema := &SubSchema{Property: KeyAnyOf, Parent: currentSchema, Ref: currentSchema.Ref}
|
||||
currentSchema.AnyOf = append(currentSchema.AnyOf, newSchema)
|
||||
err := d.parseSchema(v, newSchema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
allOf, err := getSlice(m, KeyAllOf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range allOf {
|
||||
newSchema := &SubSchema{Property: KeyAllOf, Parent: currentSchema, Ref: currentSchema.Ref}
|
||||
currentSchema.AllOf = append(currentSchema.AllOf, newSchema)
|
||||
err := d.parseSchema(v, newSchema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if vNot, found := m[KeyNot]; found {
|
||||
switch vNot.(type) {
|
||||
case bool, map[string]any:
|
||||
newSchema := &SubSchema{Property: KeyNot, Parent: currentSchema, Ref: currentSchema.Ref}
|
||||
currentSchema.not = newSchema
|
||||
err := d.parseSchema(vNot, newSchema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.MustBeOfAn(),
|
||||
ErrorDetails{"x": KeyNot, "y": TypeObject},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
if *currentSchema.Draft >= Draft7 {
|
||||
if vIf, found := m[KeyIf]; found {
|
||||
switch vIf.(type) {
|
||||
case bool, map[string]any:
|
||||
newSchema := &SubSchema{Property: KeyIf, Parent: currentSchema, Ref: currentSchema.Ref}
|
||||
currentSchema._if = newSchema
|
||||
err := d.parseSchema(vIf, newSchema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.MustBeOfAn(),
|
||||
ErrorDetails{"x": KeyIf, "y": TypeObject},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
if then, found := m[KeyThen]; found {
|
||||
switch then.(type) {
|
||||
case bool, map[string]any:
|
||||
newSchema := &SubSchema{Property: KeyThen, Parent: currentSchema, Ref: currentSchema.Ref}
|
||||
currentSchema._then = newSchema
|
||||
err := d.parseSchema(then, newSchema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.MustBeOfAn(),
|
||||
ErrorDetails{"x": KeyThen, "y": TypeObject},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
if vElse, found := m[KeyElse]; found {
|
||||
switch vElse.(type) {
|
||||
case bool, map[string]any:
|
||||
newSchema := &SubSchema{Property: KeyElse, Parent: currentSchema, Ref: currentSchema.Ref}
|
||||
currentSchema._else = newSchema
|
||||
err := d.parseSchema(vElse, newSchema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.MustBeOfAn(),
|
||||
ErrorDetails{"x": KeyElse, "y": TypeObject},
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Schema) parseReference(_ any, currentSchema *SubSchema) error {
|
||||
var (
|
||||
refdDocumentNode any
|
||||
dsp *schemaPoolDocument
|
||||
err error
|
||||
)
|
||||
|
||||
newSchema := &SubSchema{Property: KeyRef, Parent: currentSchema, Ref: currentSchema.Ref}
|
||||
|
||||
d.ReferencePool.Add(currentSchema.Ref.String(), newSchema)
|
||||
|
||||
dsp, err = d.Pool.GetDocument(*currentSchema.Ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newSchema.ID = currentSchema.Ref
|
||||
|
||||
refdDocumentNode = dsp.Document
|
||||
newSchema.Draft = dsp.Draft
|
||||
|
||||
switch refdDocumentNode.(type) {
|
||||
case bool, map[string]any:
|
||||
// expected
|
||||
default:
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.MustBeOfType(),
|
||||
ErrorDetails{"key": StringSchema, "type": TypeObject},
|
||||
))
|
||||
}
|
||||
|
||||
err = d.parseSchema(refdDocumentNode, newSchema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentSchema.RefSchema = newSchema
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (d *Schema) parseProperties(documentNode any, currentSchema *SubSchema) error {
|
||||
m, isMap := documentNode.(map[string]any)
|
||||
if !isMap {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.MustBeOfType(),
|
||||
ErrorDetails{"key": StringProperties, "type": TypeObject},
|
||||
))
|
||||
}
|
||||
|
||||
for k := range m {
|
||||
schemaProperty := k
|
||||
newSchema := &SubSchema{Property: schemaProperty, Parent: currentSchema, Ref: currentSchema.Ref}
|
||||
currentSchema.PropertiesChildren = append(currentSchema.PropertiesChildren, newSchema)
|
||||
err := d.parseSchema(m[k], newSchema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Schema) parseDependencies(documentNode any, currentSchema *SubSchema) error {
|
||||
m, isMap := documentNode.(map[string]any)
|
||||
if !isMap {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.MustBeOfType(),
|
||||
ErrorDetails{"key": KeyDependencies, "type": TypeObject},
|
||||
))
|
||||
}
|
||||
currentSchema.dependencies = make(map[string]any)
|
||||
|
||||
for k := range m {
|
||||
switch values := m[k].(type) {
|
||||
case []any:
|
||||
var valuesToRegister []string
|
||||
for _, value := range values {
|
||||
str, isString := value.(string)
|
||||
if !isString {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.MustBeOfType(),
|
||||
ErrorDetails{
|
||||
"key": StringDependency,
|
||||
"type": StringSchemaOrArrayOfStrings,
|
||||
},
|
||||
))
|
||||
}
|
||||
valuesToRegister = append(valuesToRegister, str)
|
||||
currentSchema.dependencies[k] = valuesToRegister
|
||||
}
|
||||
|
||||
case bool, map[string]any:
|
||||
depSchema := &SubSchema{Property: k, Parent: currentSchema, Ref: currentSchema.Ref}
|
||||
err := d.parseSchema(m[k], depSchema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentSchema.dependencies[k] = depSchema
|
||||
|
||||
default:
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.MustBeOfType(),
|
||||
ErrorDetails{
|
||||
"key": StringDependency,
|
||||
"type": StringSchemaOrArrayOfStrings,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func invalidType(expected, given string) error {
|
||||
return errors.New(formatErrorDescription(
|
||||
Locale.InvalidType(),
|
||||
ErrorDetails{
|
||||
"expected": expected,
|
||||
"given": given,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
func getString(m map[string]any, key string) (*string, error) {
|
||||
v, found := m[key]
|
||||
if !found {
|
||||
// not found
|
||||
return nil, nil
|
||||
}
|
||||
s, isString := v.(string)
|
||||
if !isString {
|
||||
// wrong type
|
||||
return nil, invalidType(TypeString, key)
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func getMap(m map[string]any, key string) (map[string]any, error) {
|
||||
v, found := m[key]
|
||||
if !found {
|
||||
// not found
|
||||
return nil, nil
|
||||
}
|
||||
s, isMap := v.(map[string]any)
|
||||
if !isMap {
|
||||
// wrong type
|
||||
return nil, invalidType(StringSchema, key)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func getSlice(m map[string]any, key string) ([]any, error) {
|
||||
v, found := m[key]
|
||||
if !found {
|
||||
return nil, nil
|
||||
}
|
||||
s, isArray := v.([]any)
|
||||
if !isArray {
|
||||
return nil, errors.New(formatErrorDescription(
|
||||
Locale.MustBeOfAn(),
|
||||
ErrorDetails{"x": key, "y": TypeArray},
|
||||
))
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
Generated
Vendored
+206
@@ -0,0 +1,206 @@
|
||||
// Copyright 2018 johandorland ( https://github.com/johandorland )
|
||||
//
|
||||
// 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 gojsonschema
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
|
||||
"github.com/xeipuuv/gojsonreference"
|
||||
)
|
||||
|
||||
// SchemaLoader is used to load schemas
|
||||
type SchemaLoader struct {
|
||||
pool *schemaPool
|
||||
AutoDetect bool
|
||||
Validate bool
|
||||
Draft Draft
|
||||
}
|
||||
|
||||
// NewSchemaLoader creates a new NewSchemaLoader
|
||||
func NewSchemaLoader() *SchemaLoader {
|
||||
|
||||
ps := &SchemaLoader{
|
||||
pool: &schemaPool{
|
||||
schemaPoolDocuments: make(map[string]*schemaPoolDocument),
|
||||
},
|
||||
AutoDetect: true,
|
||||
Validate: false,
|
||||
Draft: Hybrid,
|
||||
}
|
||||
ps.pool.autoDetect = &ps.AutoDetect
|
||||
|
||||
return ps
|
||||
}
|
||||
|
||||
func (sl *SchemaLoader) validateMetaschema(documentNode any) error {
|
||||
|
||||
var (
|
||||
schema string
|
||||
err error
|
||||
)
|
||||
if sl.AutoDetect {
|
||||
schema, _, err = parseSchemaURL(documentNode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// If no explicit "$schema" is used, use the default metaschema associated with the draft used
|
||||
if schema == "" {
|
||||
if sl.Draft == Hybrid {
|
||||
return nil
|
||||
}
|
||||
schema = drafts.GetSchemaURL(sl.Draft)
|
||||
}
|
||||
|
||||
//Disable validation when loading the metaschema to prevent an infinite recursive loop
|
||||
sl.Validate = false
|
||||
|
||||
metaSchema, err := sl.Compile(NewReferenceLoader(schema))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sl.Validate = true
|
||||
|
||||
result := metaSchema.validateDocument(documentNode)
|
||||
|
||||
if !result.Valid() {
|
||||
var res bytes.Buffer
|
||||
for _, err := range result.Errors() {
|
||||
res.WriteString(err.String())
|
||||
res.WriteString("\n")
|
||||
}
|
||||
return errors.New(res.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddSchemas adds an arbritrary amount of schemas to the schema cache. As this function does not require
|
||||
// an explicit URL, every schema should contain an $id, so that it can be referenced by the main schema
|
||||
func (sl *SchemaLoader) AddSchemas(loaders ...JSONLoader) error {
|
||||
emptyRef, _ := gojsonreference.NewJsonReference("")
|
||||
|
||||
for _, loader := range loaders {
|
||||
doc, err := loader.LoadJSON()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sl.Validate {
|
||||
if err := sl.validateMetaschema(doc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Directly use the Recursive function, so that it get only added to the schema Pool by $id
|
||||
// and not by the ref of the document as it's empty
|
||||
if err = sl.pool.parseReferences(doc, emptyRef, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddSchema adds a schema under the provided URL to the schema cache
|
||||
func (sl *SchemaLoader) AddSchema(url string, loader JSONLoader) error {
|
||||
|
||||
ref, err := gojsonreference.NewJsonReference(url)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
doc, err := loader.LoadJSON()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sl.Validate {
|
||||
if err := sl.validateMetaschema(doc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return sl.pool.parseReferences(doc, ref, true)
|
||||
}
|
||||
|
||||
// Compile loads and compiles a schema
|
||||
func (sl *SchemaLoader) Compile(rootSchema JSONLoader) (*Schema, error) {
|
||||
|
||||
ref, err := rootSchema.JSONReference()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := Schema{}
|
||||
d.Pool = sl.pool
|
||||
d.Pool.jsonLoaderFactory = rootSchema.LoaderFactory()
|
||||
d.DocumentReference = ref
|
||||
d.ReferencePool = newSchemaReferencePool()
|
||||
|
||||
var doc any
|
||||
if ref.String() != "" {
|
||||
// Get document from schema pool
|
||||
spd, err := d.Pool.GetDocument(d.DocumentReference)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
doc = spd.Document
|
||||
} else {
|
||||
// Load JSON directly
|
||||
doc, err = rootSchema.LoadJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// References need only be parsed if loading JSON directly
|
||||
// as pool.GetDocument already does this for us if loading by reference
|
||||
err = sl.pool.parseReferences(doc, ref, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if sl.Validate {
|
||||
if err := sl.validateMetaschema(doc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
draft := sl.Draft
|
||||
if sl.AutoDetect {
|
||||
_, detectedDraft, err := parseSchemaURL(doc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if detectedDraft != nil {
|
||||
draft = *detectedDraft
|
||||
}
|
||||
}
|
||||
|
||||
err = d.parse(doc, draft)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &d, nil
|
||||
}
|
||||
Generated
Vendored
+230
@@ -0,0 +1,230 @@
|
||||
// Copyright 2015 xeipuuv ( https://github.com/xeipuuv )
|
||||
//
|
||||
// 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.
|
||||
|
||||
// author xeipuuv
|
||||
// author-github https://github.com/xeipuuv
|
||||
// author-mail xeipuuv@gmail.com
|
||||
//
|
||||
// repository-name gojsonschema
|
||||
// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language.
|
||||
//
|
||||
// description Defines resources pooling.
|
||||
// Eases referencing and avoids downloading the same resource twice.
|
||||
//
|
||||
// created 26-02-2013
|
||||
|
||||
package gojsonschema
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/xeipuuv/gojsonreference"
|
||||
)
|
||||
|
||||
type schemaPoolDocument struct {
|
||||
Document any
|
||||
Draft *Draft
|
||||
}
|
||||
|
||||
type schemaPool struct {
|
||||
schemaPoolDocuments map[string]*schemaPoolDocument
|
||||
jsonLoaderFactory JSONLoaderFactory
|
||||
autoDetect *bool
|
||||
}
|
||||
|
||||
func (p *schemaPool) parseReferences(document any, ref gojsonreference.JsonReference, pooled bool) error {
|
||||
|
||||
var (
|
||||
draft *Draft
|
||||
err error
|
||||
reference = ref.String()
|
||||
)
|
||||
// Only the root document should be added to the schema pool if pooled is true
|
||||
if _, ok := p.schemaPoolDocuments[reference]; pooled && ok {
|
||||
return fmt.Errorf("Reference already exists: \"%s\"", reference)
|
||||
}
|
||||
|
||||
if *p.autoDetect {
|
||||
_, draft, err = parseSchemaURL(document)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = p.parseReferencesRecursive(document, ref, draft)
|
||||
|
||||
if pooled {
|
||||
p.schemaPoolDocuments[reference] = &schemaPoolDocument{Document: document, Draft: draft}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *schemaPool) parseReferencesRecursive(document any, ref gojsonreference.JsonReference, draft *Draft) error {
|
||||
// parseReferencesRecursive parses a JSON document and resolves all $id and $ref references.
|
||||
// For $ref references it takes into account the $id scope it is in and replaces
|
||||
// the reference by the absolute resolved reference
|
||||
|
||||
// When encountering errors it fails silently. Error handling is done when the schema
|
||||
// is syntactically parsed and any error encountered here should also come up there.
|
||||
switch m := document.(type) {
|
||||
case []any:
|
||||
for _, v := range m {
|
||||
err := p.parseReferencesRecursive(v, ref, draft)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case map[string]any:
|
||||
localRef := &ref
|
||||
|
||||
keyID := KeyIDNew
|
||||
if _, ok := m[KeyID]; ok {
|
||||
keyID = KeyID
|
||||
}
|
||||
if v, ok := m[keyID]; ok {
|
||||
if value, isString := v.(string); isString {
|
||||
jsonReference, err := gojsonreference.NewJsonReference(value)
|
||||
if err == nil {
|
||||
localRef, err = ref.Inherits(jsonReference)
|
||||
if err == nil {
|
||||
if _, ok := p.schemaPoolDocuments[localRef.String()]; ok {
|
||||
return fmt.Errorf("Reference already exists: \"%s\"", localRef.String())
|
||||
}
|
||||
p.schemaPoolDocuments[localRef.String()] = &schemaPoolDocument{Document: document, Draft: draft}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := m[KeyRef]; ok {
|
||||
if s, isString := v.(string); isString {
|
||||
jsonReference, err := gojsonreference.NewJsonReference(s)
|
||||
if err == nil {
|
||||
absoluteRef, err := localRef.Inherits(jsonReference)
|
||||
if err == nil {
|
||||
m[KeyRef] = absoluteRef.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range m {
|
||||
// const and enums should be interpreted literally, so ignore them
|
||||
if k == KeyConst || k == KeyEnum {
|
||||
continue
|
||||
}
|
||||
// Something like a property or a dependency is not a valid schema, as it might describe properties named "$ref", "$id" or "const", etc
|
||||
// Therefore don't treat it like a schema.
|
||||
if k == KeyProperties || k == KeyDependencies || k == KeyPatternProperties {
|
||||
if child, ok := v.(map[string]any); ok {
|
||||
for _, v := range child {
|
||||
err := p.parseReferencesRecursive(v, *localRef, draft)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err := p.parseReferencesRecursive(v, *localRef, draft)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *schemaPool) GetDocument(reference gojsonreference.JsonReference) (*schemaPoolDocument, error) {
|
||||
|
||||
var (
|
||||
spd *schemaPoolDocument
|
||||
draft *Draft
|
||||
ok bool
|
||||
err error
|
||||
)
|
||||
|
||||
if internalLogEnabled {
|
||||
internalLog("Get Document ( %s )", reference.String())
|
||||
}
|
||||
|
||||
// Create a deep copy, so we can remove the fragment part later on without altering the original
|
||||
refToURL, _ := gojsonreference.NewJsonReference(reference.String())
|
||||
|
||||
// First check if the given fragment is a location independent identifier
|
||||
// http://json-schema.org/latest/json-schema-core.html#rfc.section.8.2.3
|
||||
|
||||
if spd, ok = p.schemaPoolDocuments[refToURL.String()]; ok {
|
||||
if internalLogEnabled {
|
||||
internalLog(" From pool")
|
||||
}
|
||||
return spd, nil
|
||||
}
|
||||
|
||||
// If the given reference is not a location independent identifier,
|
||||
// strip the fragment and look for a document with it's base URI
|
||||
|
||||
refToURL.GetUrl().Fragment = ""
|
||||
|
||||
if cachedSpd, ok := p.schemaPoolDocuments[refToURL.String()]; ok {
|
||||
document, _, err := reference.GetPointer().Get(cachedSpd.Document)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if internalLogEnabled {
|
||||
internalLog(" From pool")
|
||||
}
|
||||
|
||||
spd = &schemaPoolDocument{Document: document, Draft: cachedSpd.Draft}
|
||||
p.schemaPoolDocuments[reference.String()] = spd
|
||||
|
||||
return spd, nil
|
||||
}
|
||||
|
||||
// It is not possible to load anything remotely that is not canonical...
|
||||
if !reference.IsCanonical() {
|
||||
return nil, errors.New(formatErrorDescription(
|
||||
Locale.ReferenceMustBeCanonical(),
|
||||
ErrorDetails{"reference": reference.String()},
|
||||
))
|
||||
}
|
||||
|
||||
jsonReferenceLoader := p.jsonLoaderFactory.New(reference.String())
|
||||
document, err := jsonReferenceLoader.LoadJSON()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// add the whole document to the pool for potential re-use
|
||||
err = p.parseReferences(document, refToURL, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, draft, _ = parseSchemaURL(document)
|
||||
|
||||
// resolve the potential fragment and also cache it
|
||||
document, _, err = reference.GetPointer().Get(document)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &schemaPoolDocument{Document: document, Draft: draft}, nil
|
||||
}
|
||||
Generated
Vendored
+64
@@ -0,0 +1,64 @@
|
||||
// Copyright 2015 xeipuuv ( https://github.com/xeipuuv )
|
||||
//
|
||||
// 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.
|
||||
|
||||
// author xeipuuv
|
||||
// author-github https://github.com/xeipuuv
|
||||
// author-mail xeipuuv@gmail.com
|
||||
//
|
||||
// repository-name gojsonschema
|
||||
// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language.
|
||||
//
|
||||
// description Pool of referenced schemas.
|
||||
//
|
||||
// created 25-06-2013
|
||||
|
||||
package gojsonschema
|
||||
|
||||
type schemaReferencePool struct {
|
||||
documents map[string]*SubSchema
|
||||
}
|
||||
|
||||
func newSchemaReferencePool() *schemaReferencePool {
|
||||
|
||||
p := &schemaReferencePool{}
|
||||
p.documents = make(map[string]*SubSchema)
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *schemaReferencePool) Get(ref string) (r *SubSchema, o bool) {
|
||||
|
||||
if internalLogEnabled {
|
||||
internalLog("Schema Reference ( %s )", ref)
|
||||
}
|
||||
|
||||
if sch, ok := p.documents[ref]; ok {
|
||||
if internalLogEnabled {
|
||||
internalLog(" From pool")
|
||||
}
|
||||
return sch, true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (p *schemaReferencePool) Add(ref string, sch *SubSchema) {
|
||||
|
||||
if internalLogEnabled {
|
||||
internalLog("Add Schema Reference %s to pool", ref)
|
||||
}
|
||||
if _, ok := p.documents[ref]; !ok {
|
||||
p.documents[ref] = sch
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+78
@@ -0,0 +1,78 @@
|
||||
// Copyright 2015 xeipuuv ( https://github.com/xeipuuv )
|
||||
//
|
||||
// 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.
|
||||
|
||||
// author xeipuuv
|
||||
// author-github https://github.com/xeipuuv
|
||||
// author-mail xeipuuv@gmail.com
|
||||
//
|
||||
// repository-name gojsonschema
|
||||
// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language.
|
||||
//
|
||||
// description Helper structure to handle schema types, and the combination of them.
|
||||
//
|
||||
// created 28-02-2013
|
||||
|
||||
package gojsonschema
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type jsonSchemaType struct {
|
||||
types []string
|
||||
}
|
||||
|
||||
// Is the schema typed ? that is containing at least one type
|
||||
// When not typed, the schema does not need any type validation
|
||||
func (t *jsonSchemaType) IsTyped() bool {
|
||||
return len(t.types) > 0
|
||||
}
|
||||
|
||||
func (t *jsonSchemaType) Add(etype string) error {
|
||||
|
||||
if !isStringInSlice(JSONTypes, etype) {
|
||||
return errors.New(formatErrorDescription(Locale.NotAValidType(), ErrorDetails{"given": "/" + etype + "/", "expected": JSONTypes}))
|
||||
}
|
||||
|
||||
if t.Contains(etype) {
|
||||
return errors.New(formatErrorDescription(Locale.Duplicated(), ErrorDetails{"type": etype}))
|
||||
}
|
||||
|
||||
t.types = append(t.types, etype)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *jsonSchemaType) Contains(etype string) bool {
|
||||
|
||||
return slices.Contains(t.types, etype)
|
||||
}
|
||||
|
||||
func (t *jsonSchemaType) String() string {
|
||||
|
||||
if len(t.types) == 0 {
|
||||
return StringUndefined // should never happen
|
||||
}
|
||||
|
||||
// Displayed as a list [type1,type2,...]
|
||||
if len(t.types) > 1 {
|
||||
return fmt.Sprintf("[%s]", strings.Join(t.types, ","))
|
||||
}
|
||||
|
||||
// Only one type: name only
|
||||
return t.types[0]
|
||||
}
|
||||
Generated
Vendored
+151
@@ -0,0 +1,151 @@
|
||||
// Copyright 2015 xeipuuv ( https://github.com/xeipuuv )
|
||||
//
|
||||
// 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.
|
||||
|
||||
// author xeipuuv
|
||||
// author-github https://github.com/xeipuuv
|
||||
// author-mail xeipuuv@gmail.com
|
||||
//
|
||||
// repository-name gojsonschema
|
||||
// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language.
|
||||
//
|
||||
// description Defines the structure of a sub-SubSchema.
|
||||
// A sub-SubSchema can contain other sub-schemas.
|
||||
//
|
||||
// created 27-02-2013
|
||||
|
||||
package gojsonschema
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"regexp"
|
||||
|
||||
"github.com/xeipuuv/gojsonreference"
|
||||
)
|
||||
|
||||
// Constants
|
||||
const (
|
||||
KeySchema = "$schema"
|
||||
KeyID = "id"
|
||||
KeyIDNew = "$id"
|
||||
KeyRef = "$ref"
|
||||
KeyTitle = "title"
|
||||
KeyDescription = "description"
|
||||
KeyType = "type"
|
||||
KeyItems = "items"
|
||||
KeyAdditionalItems = "additionalItems"
|
||||
KeyProperties = "properties"
|
||||
KeyPatternProperties = "patternProperties"
|
||||
KeyAdditionalProperties = "additionalProperties"
|
||||
KeyPropertyNames = "propertyNames"
|
||||
KeyDefinitions = "definitions"
|
||||
KeyMultipleOf = "multipleOf"
|
||||
KeyMinimum = "minimum"
|
||||
KeyMaximum = "maximum"
|
||||
KeyExclusiveMinimum = "exclusiveMinimum"
|
||||
KeyExclusiveMaximum = "exclusiveMaximum"
|
||||
KeyMinLength = "minLength"
|
||||
KeyMaxLength = "maxLength"
|
||||
KeyPattern = "pattern"
|
||||
KeyFormat = "format"
|
||||
KeyMinProperties = "minProperties"
|
||||
KeyMaxProperties = "maxProperties"
|
||||
KeyDependencies = "dependencies"
|
||||
KeyRequired = "required"
|
||||
KeyMinItems = "minItems"
|
||||
KeyMaxItems = "maxItems"
|
||||
KeyUniqueItems = "uniqueItems"
|
||||
KeyContains = "contains"
|
||||
KeyConst = "const"
|
||||
KeyEnum = "enum"
|
||||
KeyOneOf = "oneOf"
|
||||
KeyAnyOf = "anyOf"
|
||||
KeyAllOf = "allOf"
|
||||
KeyNot = "not"
|
||||
KeyIf = "if"
|
||||
KeyThen = "then"
|
||||
KeyElse = "else"
|
||||
)
|
||||
|
||||
// SubSchema holds a sub schema
|
||||
type SubSchema struct {
|
||||
Draft *Draft
|
||||
|
||||
// basic SubSchema meta properties
|
||||
ID *gojsonreference.JsonReference
|
||||
title *string
|
||||
description *string
|
||||
|
||||
Property string
|
||||
|
||||
// Quick pass/fail for boolean schemas
|
||||
pass *bool
|
||||
|
||||
// Types associated with the SubSchema
|
||||
Types jsonSchemaType
|
||||
|
||||
// Reference url
|
||||
Ref *gojsonreference.JsonReference
|
||||
// Schema referenced
|
||||
RefSchema *SubSchema
|
||||
|
||||
// hierarchy
|
||||
Parent *SubSchema
|
||||
ItemsChildren []*SubSchema
|
||||
ItemsChildrenIsSingleSchema bool
|
||||
PropertiesChildren []*SubSchema
|
||||
|
||||
// validation : number / integer
|
||||
multipleOf *big.Rat
|
||||
maximum *big.Rat
|
||||
exclusiveMaximum *big.Rat
|
||||
minimum *big.Rat
|
||||
exclusiveMinimum *big.Rat
|
||||
|
||||
// validation : string
|
||||
minLength *int
|
||||
maxLength *int
|
||||
pattern *regexp.Regexp
|
||||
format string
|
||||
|
||||
// validation : object
|
||||
minProperties *int
|
||||
maxProperties *int
|
||||
required []string
|
||||
|
||||
dependencies map[string]any
|
||||
additionalProperties any
|
||||
patternProperties map[string]*SubSchema
|
||||
propertyNames *SubSchema
|
||||
|
||||
// validation : array
|
||||
minItems *int
|
||||
maxItems *int
|
||||
uniqueItems bool
|
||||
contains *SubSchema
|
||||
|
||||
additionalItems any
|
||||
|
||||
// validation : all
|
||||
_const *string //const is a golang keyword
|
||||
enum []string
|
||||
|
||||
// validation : SubSchema
|
||||
oneOf []*SubSchema
|
||||
AnyOf []*SubSchema
|
||||
AllOf []*SubSchema
|
||||
not *SubSchema
|
||||
_if *SubSchema // if/else are golang keywords
|
||||
_then *SubSchema
|
||||
_else *SubSchema
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
// Copyright 2015 xeipuuv ( https://github.com/xeipuuv )
|
||||
//
|
||||
// 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.
|
||||
|
||||
// author xeipuuv
|
||||
// author-github https://github.com/xeipuuv
|
||||
// author-mail xeipuuv@gmail.com
|
||||
//
|
||||
// repository-name gojsonschema
|
||||
// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language.
|
||||
//
|
||||
// description Contains const types for schema and JSON.
|
||||
//
|
||||
// created 28-02-2013
|
||||
|
||||
package gojsonschema
|
||||
|
||||
// Type constants
|
||||
const (
|
||||
TypeArray = `array`
|
||||
TypeBoolean = `boolean`
|
||||
TypeInteger = `integer`
|
||||
TypeNumber = `number`
|
||||
TypeNull = `null`
|
||||
TypeObject = `object`
|
||||
TypeString = `string`
|
||||
)
|
||||
|
||||
// JSONTypes hosts the list of type that are supported in JSON
|
||||
var JSONTypes []string
|
||||
|
||||
// SchemaTypes Hosts The List Of Type That Are Supported In Schemas
|
||||
var SchemaTypes []string
|
||||
|
||||
func init() {
|
||||
JSONTypes = []string{
|
||||
TypeArray,
|
||||
TypeBoolean,
|
||||
TypeInteger,
|
||||
TypeNumber,
|
||||
TypeNull,
|
||||
TypeObject,
|
||||
TypeString}
|
||||
|
||||
SchemaTypes = []string{
|
||||
TypeArray,
|
||||
TypeBoolean,
|
||||
TypeInteger,
|
||||
TypeNumber,
|
||||
TypeObject,
|
||||
TypeString}
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
// Copyright 2015 xeipuuv ( https://github.com/xeipuuv )
|
||||
//
|
||||
// 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.
|
||||
|
||||
// author xeipuuv
|
||||
// author-github https://github.com/xeipuuv
|
||||
// author-mail xeipuuv@gmail.com
|
||||
//
|
||||
// repository-name gojsonschema
|
||||
// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language.
|
||||
//
|
||||
// description Various utility functions.
|
||||
//
|
||||
// created 26-02-2013
|
||||
|
||||
// nolint:unused // Package in development (2021).
|
||||
package gojsonschema
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"slices"
|
||||
)
|
||||
|
||||
func isStringInSlice(s []string, what string) bool {
|
||||
return slices.Contains(s, what)
|
||||
}
|
||||
|
||||
func marshalToJSONString(value any) (*string, error) {
|
||||
|
||||
mBytes, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sBytes := string(mBytes)
|
||||
return &sBytes, nil
|
||||
}
|
||||
|
||||
func marshalWithoutNumber(value any) (*string, error) {
|
||||
|
||||
// The JSON is decoded using https://golang.org/pkg/encoding/json/#Decoder.UseNumber
|
||||
// This means the numbers are internally still represented as strings and therefore 1.00 is unequal to 1
|
||||
// One way to eliminate these differences is to decode and encode the JSON one more time without Decoder.UseNumber
|
||||
// so that these differences in representation are removed
|
||||
|
||||
jsonString, err := marshalToJSONString(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var document any
|
||||
|
||||
err = json.Unmarshal([]byte(*jsonString), &document)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return marshalToJSONString(document)
|
||||
}
|
||||
|
||||
func isJSONNumber(what any) bool {
|
||||
|
||||
switch what.(type) {
|
||||
|
||||
case json.Number:
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func checkJSONInteger(what any) (isInt bool) {
|
||||
|
||||
jsonNumber := what.(json.Number)
|
||||
|
||||
bigFloat, isValidNumber := new(big.Rat).SetString(string(jsonNumber))
|
||||
|
||||
return isValidNumber && bigFloat.IsInt()
|
||||
|
||||
}
|
||||
|
||||
// same as ECMA Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER
|
||||
const (
|
||||
maxJSONFloat = float64(1<<53 - 1) // 9007199254740991.0 2^53 - 1
|
||||
minJSONFloat = -float64(1<<53 - 1) //-9007199254740991.0 -2^53 - 1
|
||||
)
|
||||
|
||||
func mustBeInteger(what any) *int {
|
||||
number, ok := what.(json.Number)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
isInt := checkJSONInteger(number)
|
||||
if !isInt {
|
||||
return nil
|
||||
}
|
||||
|
||||
int64Value, err := number.Int64()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// This doesn't actually convert to an int32 value; it converts to the
|
||||
// system-specific default integer. Assuming this is a valid int32 could cause
|
||||
// bugs.
|
||||
int32Value := int(int64Value)
|
||||
return &int32Value
|
||||
}
|
||||
|
||||
func mustBeNumber(what any) *big.Rat {
|
||||
number, ok := what.(json.Number)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
float64Value, success := new(big.Rat).SetString(string(number))
|
||||
if success {
|
||||
return float64Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertDocumentNode(val any) any {
|
||||
|
||||
if lval, ok := val.([]any); ok {
|
||||
|
||||
res := []any{}
|
||||
for _, v := range lval {
|
||||
res = append(res, convertDocumentNode(v))
|
||||
}
|
||||
|
||||
return res
|
||||
|
||||
}
|
||||
|
||||
if mval, ok := val.(map[any]any); ok {
|
||||
|
||||
res := map[string]any{}
|
||||
|
||||
for k, v := range mval {
|
||||
res[k.(string)] = convertDocumentNode(v)
|
||||
}
|
||||
|
||||
return res
|
||||
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
Generated
Vendored
+837
@@ -0,0 +1,837 @@
|
||||
// Copyright 2015 xeipuuv ( https://github.com/xeipuuv )
|
||||
//
|
||||
// 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.
|
||||
|
||||
// author xeipuuv
|
||||
// author-github https://github.com/xeipuuv
|
||||
// author-mail xeipuuv@gmail.com
|
||||
//
|
||||
// repository-name gojsonschema
|
||||
// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language.
|
||||
//
|
||||
// description Extends Schema and SubSchema, implements the validation phase.
|
||||
//
|
||||
// created 28-02-2013
|
||||
|
||||
package gojsonschema
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Validate loads and validates a JSON schema
|
||||
func Validate(ls JSONLoader, ld JSONLoader) (*Result, error) {
|
||||
// load schema
|
||||
schema, err := NewSchema(ls)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return schema.Validate(ld)
|
||||
}
|
||||
|
||||
// Validate loads and validates a JSON document
|
||||
func (v *Schema) Validate(l JSONLoader) (*Result, error) {
|
||||
root, err := l.LoadJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v.validateDocument(root), nil
|
||||
}
|
||||
|
||||
func (v *Schema) validateDocument(root any) *Result {
|
||||
result := &Result{}
|
||||
context := NewJSONContext(StringContextRoot, nil)
|
||||
v.RootSchema.validateRecursive(v.RootSchema, root, result, context)
|
||||
return result
|
||||
}
|
||||
|
||||
func (v *SubSchema) subValidateWithContext(document any, context *JSONContext) *Result {
|
||||
result := &Result{}
|
||||
v.validateRecursive(v, document, result, context)
|
||||
return result
|
||||
}
|
||||
|
||||
// Walker function to validate the json recursively against the SubSchema
|
||||
func (v *SubSchema) validateRecursive(currentSubSchema *SubSchema, currentNode any, result *Result, context *JSONContext) {
|
||||
|
||||
if internalLogEnabled {
|
||||
internalLog("validateRecursive %s", context.String())
|
||||
internalLog(" %v", currentNode)
|
||||
}
|
||||
|
||||
// Handle true/false schema as early as possible as all other fields will be nil
|
||||
if currentSubSchema.pass != nil {
|
||||
if !*currentSubSchema.pass {
|
||||
result.addInternalError(
|
||||
new(FalseError),
|
||||
context,
|
||||
currentNode,
|
||||
ErrorDetails{},
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Handle referenced schemas, returns directly when a $ref is found
|
||||
if currentSubSchema.RefSchema != nil {
|
||||
v.validateRecursive(currentSubSchema.RefSchema, currentNode, result, context)
|
||||
return
|
||||
}
|
||||
|
||||
// Check for null value
|
||||
if currentNode == nil {
|
||||
if currentSubSchema.Types.IsTyped() && !currentSubSchema.Types.Contains(TypeNull) {
|
||||
result.addInternalError(
|
||||
new(InvalidTypeError),
|
||||
context,
|
||||
currentNode,
|
||||
ErrorDetails{
|
||||
"expected": currentSubSchema.Types.String(),
|
||||
"given": TypeNull,
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
currentSubSchema.validateSchema(currentSubSchema, currentNode, result, context)
|
||||
v.validateCommon(currentSubSchema, currentNode, result, context)
|
||||
|
||||
} else { // Not a null value
|
||||
|
||||
if value, isNumber := currentNode.(json.Number); isNumber {
|
||||
isInt := checkJSONInteger(value)
|
||||
|
||||
validType := currentSubSchema.Types.Contains(TypeNumber) || (isInt && currentSubSchema.Types.Contains(TypeInteger))
|
||||
|
||||
if currentSubSchema.Types.IsTyped() && !validType {
|
||||
|
||||
givenType := TypeInteger
|
||||
if !isInt {
|
||||
givenType = TypeNumber
|
||||
}
|
||||
|
||||
result.addInternalError(
|
||||
new(InvalidTypeError),
|
||||
context,
|
||||
currentNode,
|
||||
ErrorDetails{
|
||||
"expected": currentSubSchema.Types.String(),
|
||||
"given": givenType,
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
currentSubSchema.validateSchema(currentSubSchema, value, result, context)
|
||||
v.validateNumber(currentSubSchema, value, result, context)
|
||||
v.validateCommon(currentSubSchema, value, result, context)
|
||||
v.validateString(currentSubSchema, value, result, context)
|
||||
|
||||
} else {
|
||||
|
||||
rValue := reflect.ValueOf(currentNode)
|
||||
rKind := rValue.Kind()
|
||||
|
||||
switch rKind {
|
||||
|
||||
// Slice => JSON array
|
||||
|
||||
case reflect.Slice:
|
||||
|
||||
if currentSubSchema.Types.IsTyped() && !currentSubSchema.Types.Contains(TypeArray) {
|
||||
result.addInternalError(
|
||||
new(InvalidTypeError),
|
||||
context,
|
||||
currentNode,
|
||||
ErrorDetails{
|
||||
"expected": currentSubSchema.Types.String(),
|
||||
"given": TypeArray,
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
castCurrentNode := currentNode.([]any)
|
||||
|
||||
currentSubSchema.validateSchema(currentSubSchema, castCurrentNode, result, context)
|
||||
|
||||
v.validateArray(currentSubSchema, castCurrentNode, result, context)
|
||||
v.validateCommon(currentSubSchema, castCurrentNode, result, context)
|
||||
|
||||
// Map => JSON object
|
||||
|
||||
case reflect.Map:
|
||||
if currentSubSchema.Types.IsTyped() && !currentSubSchema.Types.Contains(TypeObject) {
|
||||
result.addInternalError(
|
||||
new(InvalidTypeError),
|
||||
context,
|
||||
currentNode,
|
||||
ErrorDetails{
|
||||
"expected": currentSubSchema.Types.String(),
|
||||
"given": TypeObject,
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
castCurrentNode, ok := currentNode.(map[string]any)
|
||||
if !ok {
|
||||
castCurrentNode = convertDocumentNode(currentNode).(map[string]any)
|
||||
}
|
||||
|
||||
currentSubSchema.validateSchema(currentSubSchema, castCurrentNode, result, context)
|
||||
|
||||
v.validateObject(currentSubSchema, castCurrentNode, result, context)
|
||||
v.validateCommon(currentSubSchema, castCurrentNode, result, context)
|
||||
|
||||
for _, pSchema := range currentSubSchema.PropertiesChildren {
|
||||
nextNode, ok := castCurrentNode[pSchema.Property]
|
||||
if ok {
|
||||
subContext := NewJSONContext(pSchema.Property, context)
|
||||
v.validateRecursive(pSchema, nextNode, result, subContext)
|
||||
}
|
||||
}
|
||||
|
||||
// Simple JSON values : string, number, boolean
|
||||
|
||||
case reflect.Bool:
|
||||
|
||||
if currentSubSchema.Types.IsTyped() && !currentSubSchema.Types.Contains(TypeBoolean) {
|
||||
result.addInternalError(
|
||||
new(InvalidTypeError),
|
||||
context,
|
||||
currentNode,
|
||||
ErrorDetails{
|
||||
"expected": currentSubSchema.Types.String(),
|
||||
"given": TypeBoolean,
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
value := currentNode.(bool)
|
||||
|
||||
currentSubSchema.validateSchema(currentSubSchema, value, result, context)
|
||||
v.validateNumber(currentSubSchema, value, result, context)
|
||||
v.validateCommon(currentSubSchema, value, result, context)
|
||||
v.validateString(currentSubSchema, value, result, context)
|
||||
|
||||
case reflect.String:
|
||||
|
||||
if currentSubSchema.Types.IsTyped() && !currentSubSchema.Types.Contains(TypeString) {
|
||||
result.addInternalError(
|
||||
new(InvalidTypeError),
|
||||
context,
|
||||
currentNode,
|
||||
ErrorDetails{
|
||||
"expected": currentSubSchema.Types.String(),
|
||||
"given": TypeString,
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
value := currentNode.(string)
|
||||
|
||||
currentSubSchema.validateSchema(currentSubSchema, value, result, context)
|
||||
v.validateNumber(currentSubSchema, value, result, context)
|
||||
v.validateCommon(currentSubSchema, value, result, context)
|
||||
v.validateString(currentSubSchema, value, result, context)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
result.incrementScore()
|
||||
}
|
||||
|
||||
// Different kinds of validation there, SubSchema / common / array / object / string...
|
||||
func (v *SubSchema) validateSchema(currentSubSchema *SubSchema, currentNode any, result *Result, context *JSONContext) {
|
||||
|
||||
if internalLogEnabled {
|
||||
internalLog("validateSchema %s", context.String())
|
||||
internalLog(" %v", currentNode)
|
||||
}
|
||||
|
||||
if len(currentSubSchema.AnyOf) > 0 {
|
||||
|
||||
validatedAnyOf := false
|
||||
var bestValidationResult *Result
|
||||
|
||||
for _, anyOfSchema := range currentSubSchema.AnyOf {
|
||||
if !validatedAnyOf {
|
||||
validationResult := anyOfSchema.subValidateWithContext(currentNode, context)
|
||||
validatedAnyOf = validationResult.Valid()
|
||||
|
||||
if !validatedAnyOf && (bestValidationResult == nil || validationResult.score > bestValidationResult.score) {
|
||||
bestValidationResult = validationResult
|
||||
}
|
||||
}
|
||||
}
|
||||
if !validatedAnyOf {
|
||||
|
||||
result.addInternalError(new(NumberAnyOfError), context, currentNode, ErrorDetails{})
|
||||
|
||||
if bestValidationResult != nil {
|
||||
// add error messages of closest matching SubSchema as
|
||||
// that's probably the one the user was trying to match
|
||||
result.mergeErrors(bestValidationResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(currentSubSchema.oneOf) > 0 {
|
||||
|
||||
nbValidated := 0
|
||||
var bestValidationResult *Result
|
||||
|
||||
for _, oneOfSchema := range currentSubSchema.oneOf {
|
||||
validationResult := oneOfSchema.subValidateWithContext(currentNode, context)
|
||||
if validationResult.Valid() {
|
||||
nbValidated++
|
||||
} else if nbValidated == 0 && (bestValidationResult == nil || validationResult.score > bestValidationResult.score) {
|
||||
bestValidationResult = validationResult
|
||||
}
|
||||
}
|
||||
|
||||
if nbValidated != 1 {
|
||||
|
||||
result.addInternalError(new(NumberOneOfError), context, currentNode, ErrorDetails{})
|
||||
|
||||
if nbValidated == 0 {
|
||||
// add error messages of closest matching SubSchema as
|
||||
// that's probably the one the user was trying to match
|
||||
result.mergeErrors(bestValidationResult)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if len(currentSubSchema.AllOf) > 0 {
|
||||
nbValidated := 0
|
||||
|
||||
for _, allOfSchema := range currentSubSchema.AllOf {
|
||||
validationResult := allOfSchema.subValidateWithContext(currentNode, context)
|
||||
if validationResult.Valid() {
|
||||
nbValidated++
|
||||
}
|
||||
result.mergeErrors(validationResult)
|
||||
}
|
||||
|
||||
if nbValidated != len(currentSubSchema.AllOf) {
|
||||
result.addInternalError(new(NumberAllOfError), context, currentNode, ErrorDetails{})
|
||||
}
|
||||
}
|
||||
|
||||
if currentSubSchema.not != nil {
|
||||
validationResult := currentSubSchema.not.subValidateWithContext(currentNode, context)
|
||||
if validationResult.Valid() {
|
||||
result.addInternalError(new(NumberNotError), context, currentNode, ErrorDetails{})
|
||||
}
|
||||
}
|
||||
|
||||
if len(currentSubSchema.dependencies) > 0 {
|
||||
if currentNodeMap, ok := currentNode.(map[string]any); ok {
|
||||
for elementKey := range currentNodeMap {
|
||||
if dependency, ok := currentSubSchema.dependencies[elementKey]; ok {
|
||||
switch dependency := dependency.(type) {
|
||||
|
||||
case []string:
|
||||
for _, dependOnKey := range dependency {
|
||||
if _, dependencyResolved := currentNode.(map[string]any)[dependOnKey]; !dependencyResolved {
|
||||
result.addInternalError(
|
||||
new(MissingDependencyError),
|
||||
context,
|
||||
currentNode,
|
||||
ErrorDetails{"dependency": dependOnKey},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
case *SubSchema:
|
||||
dependency.validateRecursive(dependency, currentNode, result, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if currentSubSchema._if != nil {
|
||||
validationResultIf := currentSubSchema._if.subValidateWithContext(currentNode, context)
|
||||
if currentSubSchema._then != nil && validationResultIf.Valid() {
|
||||
validationResultThen := currentSubSchema._then.subValidateWithContext(currentNode, context)
|
||||
if !validationResultThen.Valid() {
|
||||
result.addInternalError(new(ConditionThenError), context, currentNode, ErrorDetails{})
|
||||
result.mergeErrors(validationResultThen)
|
||||
}
|
||||
}
|
||||
if currentSubSchema._else != nil && !validationResultIf.Valid() {
|
||||
validationResultElse := currentSubSchema._else.subValidateWithContext(currentNode, context)
|
||||
if !validationResultElse.Valid() {
|
||||
result.addInternalError(new(ConditionElseError), context, currentNode, ErrorDetails{})
|
||||
result.mergeErrors(validationResultElse)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.incrementScore()
|
||||
}
|
||||
|
||||
func (v *SubSchema) validateCommon(currentSubSchema *SubSchema, value any, result *Result, context *JSONContext) {
|
||||
|
||||
if internalLogEnabled {
|
||||
internalLog("validateCommon %s", context.String())
|
||||
internalLog(" %v", value)
|
||||
}
|
||||
|
||||
// const:
|
||||
if currentSubSchema._const != nil {
|
||||
vString, err := marshalWithoutNumber(value)
|
||||
if err != nil {
|
||||
result.addInternalError(new(InternalError), context, value, ErrorDetails{"error": err})
|
||||
}
|
||||
if *vString != *currentSubSchema._const {
|
||||
result.addInternalError(new(ConstError),
|
||||
context,
|
||||
value,
|
||||
ErrorDetails{
|
||||
"allowed": *currentSubSchema._const,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// enum:
|
||||
if len(currentSubSchema.enum) > 0 {
|
||||
vString, err := marshalWithoutNumber(value)
|
||||
if err != nil {
|
||||
result.addInternalError(new(InternalError), context, value, ErrorDetails{"error": err})
|
||||
}
|
||||
if !isStringInSlice(currentSubSchema.enum, *vString) {
|
||||
result.addInternalError(
|
||||
new(EnumError),
|
||||
context,
|
||||
value,
|
||||
ErrorDetails{
|
||||
"allowed": strings.Join(currentSubSchema.enum, ", "),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// format:
|
||||
if currentSubSchema.format != "" {
|
||||
if !FormatCheckers.IsFormat(currentSubSchema.format, value) {
|
||||
result.addInternalError(
|
||||
new(DoesNotMatchFormatError),
|
||||
context,
|
||||
value,
|
||||
ErrorDetails{"format": currentSubSchema.format},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
result.incrementScore()
|
||||
}
|
||||
|
||||
func (v *SubSchema) validateArray(currentSubSchema *SubSchema, value []any, result *Result, context *JSONContext) {
|
||||
|
||||
if internalLogEnabled {
|
||||
internalLog("validateArray %s", context.String())
|
||||
internalLog(" %v", value)
|
||||
}
|
||||
|
||||
nbValues := len(value)
|
||||
|
||||
// TODO explain
|
||||
if currentSubSchema.ItemsChildrenIsSingleSchema {
|
||||
for i := range value {
|
||||
subContext := NewJSONContext(strconv.Itoa(i), context)
|
||||
validationResult := currentSubSchema.ItemsChildren[0].subValidateWithContext(value[i], subContext)
|
||||
result.mergeErrors(validationResult)
|
||||
}
|
||||
} else {
|
||||
if len(currentSubSchema.ItemsChildren) > 0 {
|
||||
|
||||
nbItems := len(currentSubSchema.ItemsChildren)
|
||||
|
||||
// while we have both schemas and values, check them against each other
|
||||
for i := 0; i != nbItems && i != nbValues; i++ {
|
||||
subContext := NewJSONContext(strconv.Itoa(i), context)
|
||||
validationResult := currentSubSchema.ItemsChildren[i].subValidateWithContext(value[i], subContext)
|
||||
result.mergeErrors(validationResult)
|
||||
}
|
||||
|
||||
if nbItems < nbValues {
|
||||
// we have less schemas than elements in the instance array,
|
||||
// but that might be ok if "additionalItems" is specified.
|
||||
|
||||
switch currentSubSchema.additionalItems.(type) {
|
||||
case bool:
|
||||
if !currentSubSchema.additionalItems.(bool) {
|
||||
result.addInternalError(new(ArrayNoAdditionalItemsError), context, value, ErrorDetails{})
|
||||
}
|
||||
case *SubSchema:
|
||||
additionalItemSchema := currentSubSchema.additionalItems.(*SubSchema)
|
||||
for i := nbItems; i != nbValues; i++ {
|
||||
subContext := NewJSONContext(strconv.Itoa(i), context)
|
||||
validationResult := additionalItemSchema.subValidateWithContext(value[i], subContext)
|
||||
result.mergeErrors(validationResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// minItems & maxItems
|
||||
if currentSubSchema.minItems != nil {
|
||||
if nbValues < *currentSubSchema.minItems {
|
||||
result.addInternalError(
|
||||
new(ArrayMinItemsError),
|
||||
context,
|
||||
value,
|
||||
ErrorDetails{"min": *currentSubSchema.minItems},
|
||||
)
|
||||
}
|
||||
}
|
||||
if currentSubSchema.maxItems != nil {
|
||||
if nbValues > *currentSubSchema.maxItems {
|
||||
result.addInternalError(
|
||||
new(ArrayMaxItemsError),
|
||||
context,
|
||||
value,
|
||||
ErrorDetails{"max": *currentSubSchema.maxItems},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// uniqueItems:
|
||||
if currentSubSchema.uniqueItems {
|
||||
var stringifiedItems = make(map[string]int)
|
||||
for j, v := range value {
|
||||
vString, err := marshalWithoutNumber(v)
|
||||
if err != nil {
|
||||
result.addInternalError(new(InternalError), context, value, ErrorDetails{"err": err})
|
||||
}
|
||||
if i, ok := stringifiedItems[*vString]; ok {
|
||||
result.addInternalError(
|
||||
new(ItemsMustBeUniqueError),
|
||||
context,
|
||||
value,
|
||||
ErrorDetails{"type": TypeArray, "i": i, "j": j},
|
||||
)
|
||||
}
|
||||
stringifiedItems[*vString] = j
|
||||
}
|
||||
}
|
||||
|
||||
// contains:
|
||||
|
||||
if currentSubSchema.contains != nil {
|
||||
validatedOne := false
|
||||
var bestValidationResult *Result
|
||||
|
||||
for i, v := range value {
|
||||
subContext := NewJSONContext(strconv.Itoa(i), context)
|
||||
|
||||
validationResult := currentSubSchema.contains.subValidateWithContext(v, subContext)
|
||||
if validationResult.Valid() {
|
||||
validatedOne = true
|
||||
break
|
||||
}
|
||||
|
||||
if bestValidationResult == nil || validationResult.score > bestValidationResult.score {
|
||||
bestValidationResult = validationResult
|
||||
}
|
||||
}
|
||||
if !validatedOne {
|
||||
result.addInternalError(
|
||||
new(ArrayContainsError),
|
||||
context,
|
||||
value,
|
||||
ErrorDetails{},
|
||||
)
|
||||
if bestValidationResult != nil {
|
||||
result.mergeErrors(bestValidationResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.incrementScore()
|
||||
}
|
||||
|
||||
func (v *SubSchema) validateObject(currentSubSchema *SubSchema, value map[string]any, result *Result, context *JSONContext) {
|
||||
|
||||
if internalLogEnabled {
|
||||
internalLog("validateObject %s", context.String())
|
||||
internalLog(" %v", value)
|
||||
}
|
||||
|
||||
// minProperties & maxProperties:
|
||||
if currentSubSchema.minProperties != nil {
|
||||
if len(value) < *currentSubSchema.minProperties {
|
||||
result.addInternalError(
|
||||
new(ArrayMinPropertiesError),
|
||||
context,
|
||||
value,
|
||||
ErrorDetails{"min": *currentSubSchema.minProperties},
|
||||
)
|
||||
}
|
||||
}
|
||||
if currentSubSchema.maxProperties != nil {
|
||||
if len(value) > *currentSubSchema.maxProperties {
|
||||
result.addInternalError(
|
||||
new(ArrayMaxPropertiesError),
|
||||
context,
|
||||
value,
|
||||
ErrorDetails{"max": *currentSubSchema.maxProperties},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// required:
|
||||
for _, requiredProperty := range currentSubSchema.required {
|
||||
_, ok := value[requiredProperty]
|
||||
if ok {
|
||||
result.incrementScore()
|
||||
} else {
|
||||
result.addInternalError(
|
||||
new(RequiredError),
|
||||
context,
|
||||
value,
|
||||
ErrorDetails{"property": requiredProperty},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// additionalProperty & patternProperty:
|
||||
for pk := range value {
|
||||
|
||||
// Check whether this property is described by "properties"
|
||||
found := false
|
||||
for _, spValue := range currentSubSchema.PropertiesChildren {
|
||||
if pk == spValue.Property {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether this property is described by "patternProperties"
|
||||
ppMatch := v.validatePatternProperty(currentSubSchema, pk, value[pk], result, context)
|
||||
|
||||
// If it is not described by neither "properties" nor "patternProperties" it must pass "additionalProperties"
|
||||
if !found && !ppMatch {
|
||||
switch ap := currentSubSchema.additionalProperties.(type) {
|
||||
case bool:
|
||||
// Handle the boolean case separately as it's cleaner to return a specific error than failing to pass the false schema
|
||||
if !ap {
|
||||
result.addInternalError(
|
||||
new(AdditionalPropertyNotAllowedError),
|
||||
context,
|
||||
value[pk],
|
||||
ErrorDetails{"property": pk},
|
||||
)
|
||||
|
||||
}
|
||||
case *SubSchema:
|
||||
validationResult := ap.subValidateWithContext(value[pk], NewJSONContext(pk, context))
|
||||
result.mergeErrors(validationResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// propertyNames:
|
||||
if currentSubSchema.propertyNames != nil {
|
||||
for pk := range value {
|
||||
validationResult := currentSubSchema.propertyNames.subValidateWithContext(pk, context)
|
||||
if !validationResult.Valid() {
|
||||
result.addInternalError(new(InvalidPropertyNameError),
|
||||
context,
|
||||
value, ErrorDetails{
|
||||
"property": pk,
|
||||
})
|
||||
result.mergeErrors(validationResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.incrementScore()
|
||||
}
|
||||
|
||||
func (v *SubSchema) validatePatternProperty(currentSubSchema *SubSchema, key string, value any, result *Result, context *JSONContext) bool {
|
||||
|
||||
if internalLogEnabled {
|
||||
internalLog("validatePatternProperty %s", context.String())
|
||||
internalLog(" %s %v", key, value)
|
||||
}
|
||||
|
||||
validated := false
|
||||
|
||||
for pk, pv := range currentSubSchema.patternProperties {
|
||||
if matches, _ := regexp.MatchString(pk, key); matches {
|
||||
validated = true
|
||||
subContext := NewJSONContext(key, context)
|
||||
validationResult := pv.subValidateWithContext(value, subContext)
|
||||
result.mergeErrors(validationResult)
|
||||
}
|
||||
}
|
||||
|
||||
if !validated {
|
||||
return false
|
||||
}
|
||||
|
||||
result.incrementScore()
|
||||
return true
|
||||
}
|
||||
|
||||
func (v *SubSchema) validateString(currentSubSchema *SubSchema, value any, result *Result, context *JSONContext) {
|
||||
|
||||
// Ignore JSON numbers
|
||||
stringValue, isString := value.(string)
|
||||
if !isString {
|
||||
return
|
||||
}
|
||||
|
||||
if internalLogEnabled {
|
||||
internalLog("validateString %s", context.String())
|
||||
internalLog(" %v", value)
|
||||
}
|
||||
|
||||
// minLength & maxLength:
|
||||
if currentSubSchema.minLength != nil {
|
||||
if utf8.RuneCountInString(stringValue) < *currentSubSchema.minLength {
|
||||
result.addInternalError(
|
||||
new(StringLengthGTEError),
|
||||
context,
|
||||
value,
|
||||
ErrorDetails{"min": *currentSubSchema.minLength},
|
||||
)
|
||||
}
|
||||
}
|
||||
if currentSubSchema.maxLength != nil {
|
||||
if utf8.RuneCountInString(stringValue) > *currentSubSchema.maxLength {
|
||||
result.addInternalError(
|
||||
new(StringLengthLTEError),
|
||||
context,
|
||||
value,
|
||||
ErrorDetails{"max": *currentSubSchema.maxLength},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// pattern:
|
||||
if currentSubSchema.pattern != nil {
|
||||
if !currentSubSchema.pattern.MatchString(stringValue) {
|
||||
result.addInternalError(
|
||||
new(DoesNotMatchPatternError),
|
||||
context,
|
||||
value,
|
||||
ErrorDetails{"pattern": currentSubSchema.pattern},
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
result.incrementScore()
|
||||
}
|
||||
|
||||
func (v *SubSchema) validateNumber(currentSubSchema *SubSchema, value any, result *Result, context *JSONContext) {
|
||||
|
||||
// Ignore non numbers
|
||||
number, isNumber := value.(json.Number)
|
||||
if !isNumber {
|
||||
return
|
||||
}
|
||||
|
||||
if internalLogEnabled {
|
||||
internalLog("validateNumber %s", context.String())
|
||||
internalLog(" %v", value)
|
||||
}
|
||||
|
||||
float64Value, _ := new(big.Rat).SetString(string(number))
|
||||
|
||||
// multipleOf:
|
||||
if currentSubSchema.multipleOf != nil {
|
||||
if q := new(big.Rat).Quo(float64Value, currentSubSchema.multipleOf); !q.IsInt() {
|
||||
result.addInternalError(
|
||||
new(MultipleOfError),
|
||||
context,
|
||||
number,
|
||||
ErrorDetails{
|
||||
"multiple": new(big.Float).SetRat(currentSubSchema.multipleOf),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//maximum & exclusiveMaximum:
|
||||
if currentSubSchema.maximum != nil {
|
||||
if float64Value.Cmp(currentSubSchema.maximum) == 1 {
|
||||
result.addInternalError(
|
||||
new(NumberLTEError),
|
||||
context,
|
||||
number,
|
||||
ErrorDetails{
|
||||
"max": new(big.Float).SetRat(currentSubSchema.maximum),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
if currentSubSchema.exclusiveMaximum != nil {
|
||||
if float64Value.Cmp(currentSubSchema.exclusiveMaximum) >= 0 {
|
||||
result.addInternalError(
|
||||
new(NumberLTError),
|
||||
context,
|
||||
number,
|
||||
ErrorDetails{
|
||||
"max": new(big.Float).SetRat(currentSubSchema.exclusiveMaximum),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//minimum & exclusiveMinimum:
|
||||
if currentSubSchema.minimum != nil {
|
||||
if float64Value.Cmp(currentSubSchema.minimum) == -1 {
|
||||
result.addInternalError(
|
||||
new(NumberGTEError),
|
||||
context,
|
||||
number,
|
||||
ErrorDetails{
|
||||
"min": new(big.Float).SetRat(currentSubSchema.minimum),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
if currentSubSchema.exclusiveMinimum != nil {
|
||||
if float64Value.Cmp(currentSubSchema.exclusiveMinimum) <= 0 {
|
||||
result.addInternalError(
|
||||
new(NumberGTError),
|
||||
context,
|
||||
number,
|
||||
ErrorDetails{
|
||||
"min": new(big.Float).SetRat(currentSubSchema.exclusiveMinimum),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
result.incrementScore()
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
// Copyright 2021 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 patch
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/storage"
|
||||
)
|
||||
|
||||
// ParsePatchPathEscaped returns a new path for the given escaped str.
|
||||
// This is based on storage.ParsePathEscaped so will do URL unescaping of
|
||||
// the provided str for backwards compatibility, but also handles the
|
||||
// specific escape strings defined in RFC 6901 (JSON Pointer) because
|
||||
// that's what's mandated by RFC 6902 (JSON Patch).
|
||||
func ParsePatchPathEscaped(str string) (path storage.Path, ok bool) {
|
||||
path, ok = storage.ParsePathEscaped(str)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for i := range path {
|
||||
// RFC 6902 section 4: "[The "path" member's] value is a string containing
|
||||
// a JSON-Pointer value [RFC6901] that references a location within the
|
||||
// target document (the "target location") where the operation is performed."
|
||||
//
|
||||
// RFC 6901 section 3: "Because the characters '~' (%x7E) and '/' (%x2F)
|
||||
// have special meanings in JSON Pointer, '~' needs to be encoded as '~0'
|
||||
// and '/' needs to be encoded as '~1' when these characters appear in a
|
||||
// reference token."
|
||||
|
||||
// RFC 6901 section 4: "Evaluation of each reference token begins by
|
||||
// decoding any escaped character sequence. This is performed by first
|
||||
// transforming any occurrence of the sequence '~1' to '/', and then
|
||||
// transforming any occurrence of the sequence '~0' to '~'. By performing
|
||||
// the substitutions in this order, an implementation avoids the error of
|
||||
// turning '~01' first into '~1' and then into '/', which would be
|
||||
// incorrect (the string '~01' correctly becomes '~1' after transformation)."
|
||||
path[i] = strings.ReplaceAll(path[i], "~1", "/")
|
||||
path[i] = strings.ReplaceAll(path[i], "~0", "~")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# Longest Common Substring
|
||||
|
||||
Original source https://github.com/vmarkovtsev/go-lcss
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
package lcss
|
||||
|
||||
import "bytes"
|
||||
|
||||
// LongestCommonSubstring returns the longest substring which is present in all the given strings.
|
||||
// https://en.wikipedia.org/wiki/Longest_common_substring_problem
|
||||
// Not to be confused with the Longest Common Subsequence.
|
||||
// Complexity:
|
||||
// * time: sum of `n_i*log(n_i)` where `n_i` is the length of each string.
|
||||
// * space: sum of `n_i`.
|
||||
// Returns a byte slice which is never a nil.
|
||||
//
|
||||
// ### Algorithm.
|
||||
// We build suffix arrays for each of the passed string and then follow the same procedure
|
||||
// as in merge sort: pick the least suffix in the lexicographical order. It is possible
|
||||
// because the suffix arrays are already sorted.
|
||||
// We record the last encountered suffixes from each of the strings and measure the longest
|
||||
// common prefix of those at each "merge sort" step.
|
||||
// The string comparisons are optimized by maintaining the char-level prefix tree of the "heads"
|
||||
// of the suffix array sequences.
|
||||
func LongestCommonSubstring(strs ...[]byte) []byte {
|
||||
strslen := len(strs)
|
||||
if strslen == 0 {
|
||||
return []byte{}
|
||||
}
|
||||
if strslen == 1 {
|
||||
return strs[0]
|
||||
}
|
||||
suffixes := make([][]int, strslen)
|
||||
for i, str := range strs {
|
||||
suffixes[i] = qsufsort(str)
|
||||
}
|
||||
return lcss(strs, suffixes)
|
||||
}
|
||||
|
||||
func lcss(strs [][]byte, suffixes [][]int) []byte {
|
||||
strslen := len(strs)
|
||||
if strslen == 0 {
|
||||
return []byte{}
|
||||
}
|
||||
if strslen == 1 {
|
||||
return strs[0]
|
||||
}
|
||||
minstrlen := len(strs[0]) // minimum length of the strings
|
||||
for _, str := range strs {
|
||||
if minstrlen > len(str) {
|
||||
minstrlen = len(str)
|
||||
}
|
||||
}
|
||||
heads := make([]int, strslen) // position in each suffix array
|
||||
boilerplate := make([][]byte, strslen) // existing suffixes in the tree
|
||||
boiling := 0 // indicates how many distinct suffix arrays are presented in `boilerplate`
|
||||
var root charNode // the character tree built on the strings from `boilerplate`
|
||||
lcs := []byte{} // our function's return value, `var lcss []byte` does *not* work
|
||||
for {
|
||||
mini := -1
|
||||
var minSuffixStr []byte
|
||||
for i, head := range heads {
|
||||
if head >= len(suffixes[i]) {
|
||||
// this suffix array has been scanned till the end
|
||||
continue
|
||||
}
|
||||
suffix := strs[i][suffixes[i][head]:]
|
||||
if minSuffixStr == nil {
|
||||
// initialize
|
||||
mini = i
|
||||
minSuffixStr = suffix
|
||||
} else if bytes.Compare(minSuffixStr, suffix) > 0 {
|
||||
// the current suffix is the smallest in the lexicographical order
|
||||
mini = i
|
||||
minSuffixStr = suffix
|
||||
}
|
||||
}
|
||||
if mini == -1 {
|
||||
// all heads exhausted
|
||||
break
|
||||
}
|
||||
if boilerplate[mini] != nil {
|
||||
// if we already have a suffix from this string, replace it with the new one
|
||||
root.Remove(boilerplate[mini])
|
||||
} else {
|
||||
// we track the number of distinct strings which have been touched
|
||||
// when `boiling` becomes strslen we can start measuring the longest common prefix
|
||||
boiling++
|
||||
}
|
||||
boilerplate[mini] = minSuffixStr
|
||||
root.Add(minSuffixStr)
|
||||
heads[mini]++
|
||||
if boiling == strslen && root.LongestCommonPrefixLength() > len(lcs) {
|
||||
// all heads > 0, the current common prefix of the suffixes is the longest
|
||||
lcs = root.LongestCommonPrefix()
|
||||
if len(lcs) == minstrlen {
|
||||
// early exit - we will never find a longer substring
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return lcs
|
||||
}
|
||||
|
||||
// charNode builds a tree of individual characters.
|
||||
// `used` is the counter for collecting garbage: those nodes which have `used`=0 are removed.
|
||||
// The root charNode always remains intact apart from `children`.
|
||||
// The tree supports 4 operations:
|
||||
// 1. Add() a new string.
|
||||
// 2. Remove() an existing string which was previously Add()-ed.
|
||||
// 3. LongestCommonPrefixLength().
|
||||
// 4. LongestCommonPrefix().
|
||||
type charNode struct {
|
||||
char byte
|
||||
children []charNode
|
||||
used int
|
||||
}
|
||||
|
||||
// Add includes a new string into the tree. We start from the root and
|
||||
// increment `used` of all the nodes we visit.
|
||||
func (cn *charNode) Add(str []byte) {
|
||||
head := cn
|
||||
for i, char := range str {
|
||||
found := false
|
||||
for j, child := range head.children {
|
||||
if child.char == char {
|
||||
head.children[j].used++
|
||||
head = &head.children[j] // -> child
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
// add the missing nodes one by one
|
||||
for _, char = range str[i:] {
|
||||
head.children = append(head.children, charNode{char: char, children: nil, used: 1})
|
||||
head = &head.children[len(head.children)-1]
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove excludes a node which was previously Add()-ed.
|
||||
// We start from the root and decrement `used` of all the nodes we visit.
|
||||
// If there is a node with `used`=0, we erase it from the parent's list of children
|
||||
// and stop traversing the tree.
|
||||
func (cn *charNode) Remove(str []byte) {
|
||||
stop := false
|
||||
head := cn
|
||||
for _, char := range str {
|
||||
for j, child := range head.children {
|
||||
if child.char != char {
|
||||
continue
|
||||
}
|
||||
head.children[j].used--
|
||||
var parent *charNode
|
||||
head, parent = &head.children[j], head // shift to the child
|
||||
if head.used == 0 {
|
||||
parent.children = append(parent.children[:j], parent.children[j+1:]...)
|
||||
// we can skip deleting the rest of the nodes - they have been already discarded
|
||||
stop = true
|
||||
}
|
||||
break
|
||||
}
|
||||
if stop {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LongestCommonPrefixLength returns the length of the longest common prefix of the strings
|
||||
// which are stored in the tree. We visit the children recursively starting from the root and
|
||||
// stop if `used` value decreases or there is more than one child.
|
||||
func (cn charNode) LongestCommonPrefixLength() int {
|
||||
var result int
|
||||
for head := cn; len(head.children) == 1 && head.children[0].used >= head.used; head = head.children[0] {
|
||||
|
||||
result++
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// LongestCommonPrefix returns the longest common prefix of the strings
|
||||
// which are stored in the tree. We compute the length by calling LongestCommonPrefixLength()
|
||||
// and then record the characters which we visit along the way from the root to the last node.
|
||||
func (cn charNode) LongestCommonPrefix() []byte {
|
||||
result := make([]byte, cn.LongestCommonPrefixLength())
|
||||
if len(result) == 0 {
|
||||
return result
|
||||
}
|
||||
var i int
|
||||
for head := cn.children[0]; ; head = head.children[0] {
|
||||
result[i] = head.char
|
||||
i++
|
||||
if i == len(result) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This algorithm is based on "Faster Suffix Sorting"
|
||||
// by N. Jesper Larsson and Kunihiko Sadakane
|
||||
// paper: http://www.larsson.dogma.net/ssrev-tr.pdf
|
||||
// code: http://www.larsson.dogma.net/qsufsort.c
|
||||
|
||||
// This algorithm computes the suffix array sa by computing its inverse.
|
||||
// Consecutive groups of suffixes in sa are labeled as sorted groups or
|
||||
// unsorted groups. For a given pass of the sorter, all suffixes are ordered
|
||||
// up to their first h characters, and sa is h-ordered. Suffixes in their
|
||||
// final positions and unambiguously sorted in h-order are in a sorted group.
|
||||
// Consecutive groups of suffixes with identical first h characters are an
|
||||
// unsorted group. In each pass of the algorithm, unsorted groups are sorted
|
||||
// according to the group number of their following suffix.
|
||||
|
||||
// In the implementation, if sa[i] is negative, it indicates that i is
|
||||
// the first element of a sorted group of length -sa[i], and can be skipped.
|
||||
// An unsorted group sa[i:k] is given the group number of the index of its
|
||||
// last element, k-1. The group numbers are stored in the inverse slice (inv),
|
||||
// and when all groups are sorted, this slice is the inverse suffix array.
|
||||
|
||||
package lcss
|
||||
|
||||
import "sort"
|
||||
|
||||
// qsufsort constructs the suffix array for a given string.
|
||||
func qsufsort(data []byte) []int {
|
||||
// initial sorting by first byte of suffix
|
||||
sa := sortedByFirstByte(data)
|
||||
if len(sa) < 2 {
|
||||
return sa
|
||||
}
|
||||
// initialize the group lookup table
|
||||
// this becomes the inverse of the suffix array when all groups are sorted
|
||||
inv := initGroups(sa, data)
|
||||
|
||||
// the index starts 1-ordered
|
||||
sufSortable := &suffixSortable{sa: sa, inv: inv, h: 1}
|
||||
|
||||
for sa[0] > -len(sa) { // until all suffixes are one big sorted group
|
||||
// The suffixes are h-ordered, make them 2*h-ordered
|
||||
pi := 0 // pi is first position of first group
|
||||
sl := 0 // sl is negated length of sorted groups
|
||||
for pi < len(sa) {
|
||||
if s := sa[pi]; s < 0 { // if pi starts sorted group
|
||||
pi -= s // skip over sorted group
|
||||
sl += s // add negated length to sl
|
||||
} else { // if pi starts unsorted group
|
||||
if sl != 0 {
|
||||
sa[pi+sl] = sl // combine sorted groups before pi
|
||||
sl = 0
|
||||
}
|
||||
pk := inv[s] + 1 // pk-1 is last position of unsorted group
|
||||
sufSortable.sa = sa[pi:pk]
|
||||
sort.Sort(sufSortable)
|
||||
sufSortable.updateGroups(pi)
|
||||
pi = pk // next group
|
||||
}
|
||||
}
|
||||
if sl != 0 { // if the array ends with a sorted group
|
||||
sa[pi+sl] = sl // combine sorted groups at end of sa
|
||||
}
|
||||
|
||||
sufSortable.h *= 2 // double sorted depth
|
||||
}
|
||||
|
||||
for i := range sa { // reconstruct suffix array from inverse
|
||||
sa[inv[i]] = i
|
||||
}
|
||||
return sa
|
||||
}
|
||||
|
||||
func sortedByFirstByte(data []byte) []int {
|
||||
// total byte counts
|
||||
var count [256]int
|
||||
for _, b := range data {
|
||||
count[b]++
|
||||
}
|
||||
// make count[b] equal index of first occurrence of b in sorted array
|
||||
sum := 0
|
||||
for b := range count {
|
||||
count[b], sum = sum, count[b]+sum
|
||||
}
|
||||
// iterate through bytes, placing index into the correct spot in sa
|
||||
sa := make([]int, len(data))
|
||||
for i, b := range data {
|
||||
sa[count[b]] = i
|
||||
count[b]++
|
||||
}
|
||||
return sa
|
||||
}
|
||||
|
||||
func initGroups(sa []int, data []byte) []int {
|
||||
// label contiguous same-letter groups with the same group number
|
||||
inv := make([]int, len(data))
|
||||
prevGroup := len(sa) - 1
|
||||
groupByte := data[sa[prevGroup]]
|
||||
for i := len(sa) - 1; i >= 0; i-- {
|
||||
if b := data[sa[i]]; b < groupByte {
|
||||
if prevGroup == i+1 {
|
||||
sa[i+1] = -1
|
||||
}
|
||||
groupByte = b
|
||||
prevGroup = i
|
||||
}
|
||||
inv[sa[i]] = prevGroup
|
||||
if prevGroup == 0 {
|
||||
sa[0] = -1
|
||||
}
|
||||
}
|
||||
// Separate out the final suffix to the start of its group.
|
||||
// This is necessary to ensure the suffix "a" is before "aba"
|
||||
// when using a potentially unstable sort.
|
||||
lastByte := data[len(data)-1]
|
||||
s := -1
|
||||
for i := range sa {
|
||||
if sa[i] >= 0 {
|
||||
if data[sa[i]] == lastByte && s == -1 {
|
||||
s = i
|
||||
}
|
||||
if sa[i] == len(sa)-1 {
|
||||
sa[i], sa[s] = sa[s], sa[i]
|
||||
inv[sa[s]] = s
|
||||
sa[s] = -1 // mark it as an isolated sorted group
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return inv
|
||||
}
|
||||
|
||||
type suffixSortable struct {
|
||||
sa []int
|
||||
inv []int
|
||||
h int
|
||||
buf []int // common scratch space
|
||||
}
|
||||
|
||||
func (x *suffixSortable) Len() int { return len(x.sa) }
|
||||
func (x *suffixSortable) Less(i, j int) bool { return x.inv[x.sa[i]+x.h] < x.inv[x.sa[j]+x.h] }
|
||||
func (x *suffixSortable) Swap(i, j int) { x.sa[i], x.sa[j] = x.sa[j], x.sa[i] }
|
||||
|
||||
func (x *suffixSortable) updateGroups(offset int) {
|
||||
bounds := x.buf[0:0]
|
||||
group := x.inv[x.sa[0]+x.h]
|
||||
for i := 1; i < len(x.sa); i++ {
|
||||
if g := x.inv[x.sa[i]+x.h]; g > group {
|
||||
bounds = append(bounds, i)
|
||||
group = g
|
||||
}
|
||||
}
|
||||
bounds = append(bounds, len(x.sa))
|
||||
x.buf = bounds
|
||||
|
||||
// update the group numberings after all new groups are determined
|
||||
prev := 0
|
||||
for _, b := range bounds {
|
||||
for i := prev; i < b; i++ {
|
||||
x.inv[x.sa[i]] = offset + b - 1
|
||||
}
|
||||
if b-prev == 1 {
|
||||
x.sa[prev] = -1
|
||||
}
|
||||
prev = b
|
||||
}
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
// 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 leb128 implements LEB128 integer encoding.
|
||||
package leb128
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// MustReadVarInt32 returns an int32 from r or panics.
|
||||
func MustReadVarInt32(r io.Reader) int32 {
|
||||
i32, err := ReadVarInt32(r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return i32
|
||||
}
|
||||
|
||||
// MustReadVarInt64 returns an int64 from r or panics.
|
||||
func MustReadVarInt64(r io.Reader) int64 {
|
||||
i64, err := ReadVarInt64(r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return i64
|
||||
}
|
||||
|
||||
// MustReadVarUint32 returns an uint32 from r or panics.
|
||||
func MustReadVarUint32(r io.Reader) uint32 {
|
||||
u32, err := ReadVarUint32(r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return u32
|
||||
}
|
||||
|
||||
// MustReadVarUint64 returns an uint64 from r or panics.
|
||||
func MustReadVarUint64(r io.Reader) uint64 {
|
||||
u64, err := ReadVarUint64(r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return u64
|
||||
}
|
||||
|
||||
// Copied rom http://dwarfstd.org/doc/Dwarf3.pdf.
|
||||
|
||||
// ReadVarUint32 tries to read a uint32 from r.
|
||||
func ReadVarUint32(r io.Reader) (uint32, error) {
|
||||
u64, err := ReadVarUint64(r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint32(u64), nil
|
||||
}
|
||||
|
||||
// ReadVarUint64 tries to read a uint64 from r.
|
||||
func ReadVarUint64(r io.Reader) (uint64, error) {
|
||||
var result uint64
|
||||
var shift uint64
|
||||
buf := make([]byte, 1)
|
||||
for {
|
||||
if _, err := r.Read(buf); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
v := uint64(buf[0])
|
||||
result |= (v & 0x7F) << shift
|
||||
if v&0x80 == 0 {
|
||||
return result, nil
|
||||
}
|
||||
shift += 7
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ReadVarInt32 tries to read a int32 from r.
|
||||
func ReadVarInt32(r io.Reader) (int32, error) {
|
||||
i64, err := ReadVarInt64(r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int32(i64), nil
|
||||
}
|
||||
|
||||
// ReadVarInt64 tries to read a int64 from r.
|
||||
func ReadVarInt64(r io.Reader) (int64, error) {
|
||||
var result int64
|
||||
var shift uint64
|
||||
size := uint64(32)
|
||||
buf := make([]byte, 1)
|
||||
for {
|
||||
if _, err := r.Read(buf); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
v := int64(buf[0])
|
||||
result |= (v & 0x7F) << shift
|
||||
shift += 7
|
||||
if v&0x80 == 0 {
|
||||
if (shift < size) && (v&0x40 != 0) {
|
||||
result |= (^0 << shift)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WriteVarUint32 writes u to w.
|
||||
func WriteVarUint32(w io.Writer, u uint32) error {
|
||||
var b []byte
|
||||
_, err := w.Write(appendUleb128(b, uint64(u)))
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteVarUint64 writes u to w.
|
||||
func WriteVarUint64(w io.Writer, u uint64) error {
|
||||
var b []byte
|
||||
_, err := w.Write(appendUleb128(b, u))
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteVarInt32 writes u to w.
|
||||
func WriteVarInt32(w io.Writer, i int32) error {
|
||||
var b []byte
|
||||
_, err := w.Write(appendSleb128(b, int64(i)))
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteVarInt64 writes u to w.
|
||||
func WriteVarInt64(w io.Writer, i int64) error {
|
||||
var b []byte
|
||||
_, err := w.Write(appendSleb128(b, i))
|
||||
return err
|
||||
}
|
||||
|
||||
// Copied from https://github.com/golang/go/blob/master/src/cmd/internal/dwarf/dwarf.go.
|
||||
|
||||
// appendUleb128 appends v to b using DWARF's unsigned LEB128 encoding.
|
||||
func appendUleb128(b []byte, v uint64) []byte {
|
||||
for {
|
||||
c := uint8(v & 0x7f)
|
||||
v >>= 7
|
||||
if v != 0 {
|
||||
c |= 0x80
|
||||
}
|
||||
b = append(b, c)
|
||||
if c&0x80 == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// appendSleb128 appends v to b using DWARF's signed LEB128 encoding.
|
||||
func appendSleb128(b []byte, v int64) []byte {
|
||||
for {
|
||||
c := uint8(v & 0x7f)
|
||||
s := uint8(v & 0x40)
|
||||
v >>= 7
|
||||
if (v != -1 || s == 0) && (v != 0 || s != 0) {
|
||||
c |= 0x80
|
||||
}
|
||||
b = append(b, c)
|
||||
if c&0x80 == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
// Copyright 2017 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 merge contains helpers to merge data structures
|
||||
// frequently encountered in OPA.
|
||||
package merge
|
||||
|
||||
// InterfaceMaps returns the result of merging a and b. If a and b cannot be
|
||||
// merged because of conflicting key-value pairs, ok is false.
|
||||
func InterfaceMaps(a map[string]any, b map[string]any) (map[string]any, bool) {
|
||||
|
||||
if a == nil {
|
||||
return b, true
|
||||
}
|
||||
|
||||
if hasConflicts(a, b) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return merge(a, b), true
|
||||
}
|
||||
|
||||
func merge(a, b map[string]any) map[string]any {
|
||||
|
||||
for k := range b {
|
||||
|
||||
add := b[k]
|
||||
exist, ok := a[k]
|
||||
if !ok {
|
||||
a[k] = add
|
||||
continue
|
||||
}
|
||||
|
||||
existObj := exist.(map[string]any)
|
||||
addObj := add.(map[string]any)
|
||||
|
||||
a[k] = merge(existObj, addObj)
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
func hasConflicts(a, b map[string]any) bool {
|
||||
for k := range b {
|
||||
|
||||
add := b[k]
|
||||
exist, ok := a[k]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
existObj, existOk := exist.(map[string]any)
|
||||
addObj, addOk := add.(map[string]any)
|
||||
if !existOk || !addOk {
|
||||
return true
|
||||
}
|
||||
|
||||
if hasConflicts(existObj, addObj) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
+2600
File diff suppressed because it is too large
Load Diff
+337
@@ -0,0 +1,337 @@
|
||||
package planner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/ast"
|
||||
)
|
||||
|
||||
// funcstack implements a simple map structure used to keep track of virtual
|
||||
// document => planned function names. The structure supports Push and Pop
|
||||
// operations so that the planner can shadow planned functions when 'with'
|
||||
// statements are found.
|
||||
// The "gen" numbers indicate the "generations"; whenever a 'with' statement
|
||||
// is planned (a new map is `Push()`ed), it will jump to a previously unused
|
||||
// number.
|
||||
type funcstack struct {
|
||||
stack []taggedPairs
|
||||
next int
|
||||
}
|
||||
|
||||
type taggedPairs struct {
|
||||
pairs map[string]string
|
||||
vars []ast.Var
|
||||
vcount int
|
||||
gen int
|
||||
}
|
||||
|
||||
func newFuncstack() *funcstack {
|
||||
return &funcstack{
|
||||
stack: []taggedPairs{
|
||||
{
|
||||
pairs: map[string]string{},
|
||||
gen: 0,
|
||||
vars: []ast.Var{
|
||||
ast.InputRootDocument.Value.(ast.Var),
|
||||
ast.DefaultRootDocument.Value.(ast.Var),
|
||||
},
|
||||
vcount: 2,
|
||||
},
|
||||
},
|
||||
next: 1}
|
||||
}
|
||||
|
||||
func (p funcstack) last() taggedPairs {
|
||||
return p.stack[len(p.stack)-1]
|
||||
}
|
||||
|
||||
func (p funcstack) argVars() int {
|
||||
return p.last().vcount
|
||||
}
|
||||
|
||||
func (p funcstack) vars() []ast.Var {
|
||||
ret := make([]ast.Var, 0, p.last().vcount)
|
||||
for i := range p.stack {
|
||||
ret = append(ret, p.stack[i].vars...)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (p funcstack) Add(key, value string) {
|
||||
p.last().pairs[key] = value
|
||||
}
|
||||
|
||||
func (p funcstack) Get(key string) (string, bool) {
|
||||
value, ok := p.last().pairs[key]
|
||||
return value, ok
|
||||
}
|
||||
|
||||
func (p *funcstack) Push(funcs map[string]string, vars []ast.Var) {
|
||||
p.stack = append(p.stack, taggedPairs{
|
||||
pairs: funcs,
|
||||
gen: p.next,
|
||||
vars: vars,
|
||||
vcount: p.last().vcount + len(vars),
|
||||
})
|
||||
p.next++
|
||||
}
|
||||
|
||||
func (p *funcstack) Pop() map[string]string {
|
||||
last := p.last()
|
||||
p.stack = p.stack[:len(p.stack)-1]
|
||||
return last.pairs
|
||||
}
|
||||
|
||||
func (p funcstack) gen() int {
|
||||
return p.last().gen
|
||||
}
|
||||
|
||||
// ruletrie implements a simple trie structure for organizing rules that may be
|
||||
// planned. The trie nodes are keyed by the rule path. The ruletrie supports
|
||||
// Push and Pop operations that allow the planner to shadow subtrees when 'with'
|
||||
// statements are found.
|
||||
type ruletrie struct {
|
||||
children map[ast.Value][]*ruletrie
|
||||
rules []*ast.Rule
|
||||
}
|
||||
|
||||
func newRuletrie() *ruletrie {
|
||||
return &ruletrie{
|
||||
children: map[ast.Value][]*ruletrie{},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ruletrie) Arity() int {
|
||||
rules := t.Rules()
|
||||
if len(rules) > 0 {
|
||||
return len(rules[0].Head.Args)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (t *ruletrie) Rules() []*ast.Rule {
|
||||
if t != nil {
|
||||
if t.rules == nil {
|
||||
return nil
|
||||
}
|
||||
rules := make([]*ast.Rule, len(t.rules), len(t.rules)+len(t.children)) // could be too little
|
||||
copy(rules, t.rules)
|
||||
|
||||
// NOTE(sr): We pull in one layer of children: the compiler ensures
|
||||
// that these are the only possible, relevant rule sources for a given
|
||||
// ref: If the trie is what we get for
|
||||
//
|
||||
// a.b.c = 1 { ... }
|
||||
// a.b[x] = 2 { ... }
|
||||
//
|
||||
// and we're retrieving a.b, we want Rules() to include the rule body
|
||||
// of a.b.c.
|
||||
// FIXME: We need to go deeper than just immediate children (?)
|
||||
for _, rs := range t.children {
|
||||
if r := rs[len(rs)-1].rules; r != nil {
|
||||
rules = append(rules, r...)
|
||||
}
|
||||
}
|
||||
return rules
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *ruletrie) Push(key ast.Ref) {
|
||||
node := t
|
||||
for i := range len(key) - 1 {
|
||||
node = node.Get(key[i].Value)
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
elem := key[len(key)-1]
|
||||
node.children[elem.Value] = append(node.children[elem.Value], nil)
|
||||
}
|
||||
|
||||
func (t *ruletrie) Pop(key ast.Ref) {
|
||||
node := t
|
||||
for i := range len(key) - 1 {
|
||||
node = node.Get(key[i].Value)
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
elem := key[len(key)-1]
|
||||
sl := node.children[elem.Value]
|
||||
node.children[elem.Value] = sl[:len(sl)-1]
|
||||
}
|
||||
|
||||
func (t *ruletrie) Insert(key ast.Ref) *ruletrie {
|
||||
node := t
|
||||
for _, elem := range key {
|
||||
child := node.Get(elem.Value)
|
||||
if child == nil {
|
||||
child = newRuletrie()
|
||||
node.children[elem.Value] = append(node.children[elem.Value], child)
|
||||
}
|
||||
node = child
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func (t *ruletrie) Lookup(key ast.Ref) *ruletrie {
|
||||
node := t
|
||||
for _, elem := range key {
|
||||
node = node.Get(elem.Value)
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func (t *ruletrie) LookupShallowest(key ast.Ref) *ruletrie {
|
||||
node := t
|
||||
for _, elem := range key {
|
||||
node = node.Get(elem.Value)
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
if len(node.rules) > 0 {
|
||||
return node
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// TODO: Collapse rules with overlapping extent to same node(?)
|
||||
func (t *ruletrie) LookupOrInsert(key ast.Ref) *ruletrie {
|
||||
if val := t.LookupShallowest(key); val != nil {
|
||||
|
||||
return val
|
||||
}
|
||||
return t.Insert(key)
|
||||
}
|
||||
|
||||
func (t *ruletrie) DescendantRules() []*ast.Rule {
|
||||
if len(t.children) == 0 {
|
||||
return t.rules
|
||||
}
|
||||
|
||||
rules := make([]*ast.Rule, len(t.rules), len(t.rules)+len(t.children)) // could be too little
|
||||
copy(rules, t.rules)
|
||||
|
||||
for _, cs := range t.children {
|
||||
for _, c := range cs {
|
||||
rules = append(rules, c.DescendantRules()...)
|
||||
}
|
||||
}
|
||||
|
||||
return rules
|
||||
}
|
||||
|
||||
func (t *ruletrie) ChildrenCount() int {
|
||||
return len(t.children)
|
||||
}
|
||||
|
||||
func (t *ruletrie) Children() []ast.Value {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
sorted := make([]ast.Value, 0, len(t.children))
|
||||
for key := range t.children {
|
||||
if t.Get(key) != nil {
|
||||
sorted = append(sorted, key)
|
||||
}
|
||||
}
|
||||
sort.Slice(sorted, func(i, j int) bool {
|
||||
return sorted[i].Compare(sorted[j]) < 0
|
||||
})
|
||||
return sorted
|
||||
}
|
||||
|
||||
func (t *ruletrie) Get(k ast.Value) *ruletrie {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
nodes := t.children[k]
|
||||
if len(nodes) == 0 {
|
||||
return nil
|
||||
}
|
||||
return nodes[len(nodes)-1]
|
||||
}
|
||||
|
||||
func (t *ruletrie) DepthFirst(f func(*ruletrie) bool) {
|
||||
if f(t) {
|
||||
return
|
||||
}
|
||||
for _, rules := range t.children {
|
||||
for i := range rules {
|
||||
rules[i].DepthFirst(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ruletrie) Depth() int {
|
||||
if len(t.Children()) == 0 {
|
||||
return 0
|
||||
}
|
||||
c := make([]int, 0, len(t.Children()))
|
||||
for _, nodes := range t.children {
|
||||
c = append(c, nodes[len(nodes)-1].Depth())
|
||||
}
|
||||
max := 0
|
||||
for i := range c {
|
||||
if max < c[i] {
|
||||
max = c[i]
|
||||
}
|
||||
}
|
||||
return max + 1
|
||||
}
|
||||
|
||||
func (t *ruletrie) String() string {
|
||||
return fmt.Sprintf("<ruletrie rules:%v children:%v>", t.rules, t.children)
|
||||
}
|
||||
|
||||
type functionMocksStack struct {
|
||||
stack []*functionMocksElem
|
||||
}
|
||||
|
||||
type functionMocksElem []frame
|
||||
|
||||
type frame map[string]*ast.Term
|
||||
|
||||
func newFunctionMocksStack() *functionMocksStack {
|
||||
stack := &functionMocksStack{}
|
||||
stack.Push()
|
||||
return stack
|
||||
}
|
||||
|
||||
func newFunctionMocksElem() *functionMocksElem {
|
||||
return &functionMocksElem{}
|
||||
}
|
||||
|
||||
func (s *functionMocksStack) Push() {
|
||||
s.stack = append(s.stack, newFunctionMocksElem())
|
||||
}
|
||||
|
||||
func (s *functionMocksStack) Pop() {
|
||||
s.stack = s.stack[:len(s.stack)-1]
|
||||
}
|
||||
|
||||
func (s *functionMocksStack) PushFrame(f frame) {
|
||||
current := s.stack[len(s.stack)-1]
|
||||
*current = append(*current, f)
|
||||
}
|
||||
|
||||
func (s *functionMocksStack) PopFrame() {
|
||||
current := s.stack[len(s.stack)-1]
|
||||
*current = (*current)[:len(*current)-1]
|
||||
}
|
||||
|
||||
func (s *functionMocksStack) Lookup(f string) *ast.Term {
|
||||
current := *s.stack[len(s.stack)-1]
|
||||
for i := len(current) - 1; i >= 0; i-- {
|
||||
if t, ok := current[i][f]; ok {
|
||||
return t
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
// Copyright 2019 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 planner
|
||||
|
||||
import (
|
||||
"github.com/open-policy-agent/opa/v1/ast"
|
||||
"github.com/open-policy-agent/opa/v1/ir"
|
||||
)
|
||||
|
||||
type varstack []map[ast.Var]ir.Local
|
||||
|
||||
func newVarstack(frames ...map[ast.Var]ir.Local) *varstack {
|
||||
vs := &varstack{}
|
||||
for _, f := range frames {
|
||||
vs.Push(f)
|
||||
}
|
||||
return vs
|
||||
}
|
||||
|
||||
func (vs varstack) GetOrElse(k ast.Var, orElse func() ir.Local) ir.Local {
|
||||
l, ok := vs.Get(k)
|
||||
if !ok {
|
||||
l = orElse()
|
||||
vs.Put(k, l)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (vs varstack) GetOrEmpty(k ast.Var) ir.Local {
|
||||
l, _ := vs.Get(k)
|
||||
return l
|
||||
}
|
||||
|
||||
func (vs varstack) Get(k ast.Var) (ir.Local, bool) {
|
||||
for i := len(vs) - 1; i >= 0; i-- {
|
||||
if l, ok := vs[i][k]; ok {
|
||||
return l, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (vs varstack) GetOpOrEmpty(k ast.Var) ir.Operand {
|
||||
l := vs.GetOrEmpty(k)
|
||||
return ir.Operand{Value: l}
|
||||
}
|
||||
|
||||
func (vs varstack) GetOp(k ast.Var) (ir.Operand, bool) {
|
||||
l, ok := vs.Get(k)
|
||||
if !ok {
|
||||
return ir.Operand{}, false
|
||||
}
|
||||
return ir.Operand{Value: l}, true
|
||||
}
|
||||
|
||||
func (vs varstack) Put(k ast.Var, v ir.Local) {
|
||||
vs[len(vs)-1][k] = v
|
||||
}
|
||||
|
||||
func (vs *varstack) Push(frame map[ast.Var]ir.Local) {
|
||||
*vs = append(*vs, frame)
|
||||
}
|
||||
|
||||
func (vs *varstack) Pop() map[ast.Var]ir.Local {
|
||||
sl := *vs
|
||||
last := sl[len(sl)-1]
|
||||
*vs = sl[:len(sl)-1]
|
||||
return last
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
AWS SDK for Go
|
||||
Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
Copyright 2014-2015 Stripe, Inc.
|
||||
Generated
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
package crypto
|
||||
|
||||
import "errors"
|
||||
|
||||
// ConstantTimeByteCompare is a constant-time byte comparison of x and y. This function performs an absolute comparison
|
||||
// if the two byte slices assuming they represent a big-endian number.
|
||||
//
|
||||
// error if len(x) != len(y)
|
||||
// -1 if x < y
|
||||
// 0 if x == y
|
||||
// +1 if x > y
|
||||
func ConstantTimeByteCompare(x, y []byte) (int, error) {
|
||||
if len(x) != len(y) {
|
||||
return 0, errors.New("slice lengths do not match")
|
||||
}
|
||||
|
||||
xLarger, yLarger := 0, 0
|
||||
|
||||
for i := range x {
|
||||
xByte, yByte := int(x[i]), int(y[i])
|
||||
|
||||
x := ((yByte - xByte) >> 8) & 1
|
||||
y := ((xByte - yByte) >> 8) & 1
|
||||
|
||||
xLarger |= x &^ yLarger
|
||||
yLarger |= y &^ xLarger
|
||||
}
|
||||
|
||||
return xLarger - yLarger, nil
|
||||
}
|
||||
Generated
Vendored
+165
@@ -0,0 +1,165 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdh"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/hmac"
|
||||
"encoding/asn1"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"math"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
type ecdsaSignature struct {
|
||||
R, S *big.Int
|
||||
}
|
||||
|
||||
// ECDSAKey takes the given elliptic curve, and private key (d) byte slice
|
||||
// and returns the private ECDSA key.
|
||||
func ECDSAKey(curve elliptic.Curve, d []byte) *ecdsa.PrivateKey {
|
||||
return ECDSAKeyFromPoint(curve, (&big.Int{}).SetBytes(d))
|
||||
}
|
||||
|
||||
// ECDSAKeyFromPoint takes the given elliptic curve and point and returns the
|
||||
// private and public keypair
|
||||
func ECDSAKeyFromPoint(curve elliptic.Curve, d *big.Int) *ecdsa.PrivateKey {
|
||||
dBytes := make([]byte, (curve.Params().BitSize+7)/8)
|
||||
d.FillBytes(dBytes)
|
||||
|
||||
privKey := &ecdsa.PrivateKey{
|
||||
PublicKey: ecdsa.PublicKey{
|
||||
Curve: curve,
|
||||
},
|
||||
D: d,
|
||||
}
|
||||
|
||||
var pubBytes []byte
|
||||
switch curve {
|
||||
case elliptic.P256():
|
||||
if ecdhPriv, err := ecdh.P256().NewPrivateKey(dBytes); err == nil {
|
||||
pubBytes = ecdhPriv.PublicKey().Bytes()
|
||||
}
|
||||
case elliptic.P384():
|
||||
if ecdhPriv, err := ecdh.P384().NewPrivateKey(dBytes); err == nil {
|
||||
pubBytes = ecdhPriv.PublicKey().Bytes()
|
||||
}
|
||||
case elliptic.P521():
|
||||
if ecdhPriv, err := ecdh.P521().NewPrivateKey(dBytes); err == nil {
|
||||
pubBytes = ecdhPriv.PublicKey().Bytes()
|
||||
}
|
||||
}
|
||||
|
||||
if len(pubBytes) > 0 {
|
||||
byteLen := (curve.Params().BitSize + 7) / 8
|
||||
privKey.X = new(big.Int).SetBytes(pubBytes[1 : 1+byteLen])
|
||||
privKey.Y = new(big.Int).SetBytes(pubBytes[1+byteLen:])
|
||||
} else {
|
||||
panic(fmt.Sprintf("unsupported curve or invalid private key: %v", curve))
|
||||
}
|
||||
|
||||
return privKey
|
||||
}
|
||||
|
||||
// mathIntToBytes writes val as a big-endian, fixed-length byte slice into out,
|
||||
// zero-padding on the left when val.Bytes() is shorter than out. This satisfies
|
||||
// the uncompressed SEC 1 encoding (0x04 || X || Y) expected by crypto/ecdh's
|
||||
// NewPublicKey: https://pkg.go.dev/crypto/ecdh#Curve.NewPublicKey
|
||||
func mathIntToBytes(val *big.Int, out []byte) {
|
||||
valBytes := val.Bytes()
|
||||
copy(out[len(out)-len(valBytes):], valBytes)
|
||||
}
|
||||
|
||||
// ECDSAPublicKey takes the provide curve and (x, y) coordinates and returns
|
||||
// *ecdsa.PublicKey. Returns an error if the given points are not on the curve.
|
||||
func ECDSAPublicKey(curve elliptic.Curve, x, y []byte) (*ecdsa.PublicKey, error) {
|
||||
xPoint := (&big.Int{}).SetBytes(x)
|
||||
yPoint := (&big.Int{}).SetBytes(y)
|
||||
|
||||
byteLen := (curve.Params().BitSize + 7) / 8
|
||||
buf := make([]byte, 1+2*byteLen)
|
||||
buf[0] = 4 // uncompressed point
|
||||
mathIntToBytes(xPoint, buf[1:1+byteLen])
|
||||
mathIntToBytes(yPoint, buf[1+byteLen:])
|
||||
|
||||
var err error
|
||||
switch curve {
|
||||
case elliptic.P256():
|
||||
_, err = ecdh.P256().NewPublicKey(buf)
|
||||
case elliptic.P384():
|
||||
_, err = ecdh.P384().NewPublicKey(buf)
|
||||
case elliptic.P521():
|
||||
_, err = ecdh.P521().NewPublicKey(buf)
|
||||
default:
|
||||
err = fmt.Errorf("unsupported curve for ECDSA: %v", curve)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("point(%v, %v) is not on the given curve", xPoint.String(), yPoint.String())
|
||||
}
|
||||
|
||||
return &ecdsa.PublicKey{
|
||||
Curve: curve,
|
||||
X: xPoint,
|
||||
Y: yPoint,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// VerifySignature takes the provided public key, hash, and asn1 encoded signature and returns
|
||||
// whether the given signature is valid.
|
||||
func VerifySignature(key *ecdsa.PublicKey, hash []byte, signature []byte) (bool, error) {
|
||||
var ecdsaSignature ecdsaSignature
|
||||
|
||||
_, err := asn1.Unmarshal(signature, &ecdsaSignature)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return ecdsa.Verify(key, hash, ecdsaSignature.R, ecdsaSignature.S), nil
|
||||
}
|
||||
|
||||
// HMACKeyDerivation provides an implementation of a NIST-800-108 of a KDF (Key Derivation Function) in Counter Mode.
|
||||
// For the purposes of this implantation HMAC is used as the PRF (Pseudorandom function), where the value of
|
||||
// `r` is defined as a 4 byte counter.
|
||||
func HMACKeyDerivation(hash func() hash.Hash, bitLen int, key []byte, label, context []byte) ([]byte, error) {
|
||||
// verify that we won't overflow the counter
|
||||
n := int64(math.Ceil((float64(bitLen) / 8) / float64(hash().Size())))
|
||||
if n > 0x7FFFFFFF {
|
||||
return nil, fmt.Errorf("unable to derive key of size %d using 32-bit counter", bitLen)
|
||||
}
|
||||
|
||||
// verify the requested bit length is not larger then the length encoding size
|
||||
if int64(bitLen) > 0x7FFFFFFF {
|
||||
return nil, errors.New("bitLen is greater than 32-bits")
|
||||
}
|
||||
|
||||
fixedInput := bytes.NewBuffer(nil)
|
||||
fixedInput.Write(label)
|
||||
fixedInput.WriteByte(0x00)
|
||||
fixedInput.Write(context)
|
||||
if err := binary.Write(fixedInput, binary.BigEndian, int32(bitLen)); err != nil {
|
||||
return nil, fmt.Errorf("failed to write bit length to fixed input string: %v", err)
|
||||
}
|
||||
|
||||
var output []byte
|
||||
|
||||
h := hmac.New(hash, key)
|
||||
|
||||
for i := int64(1); i <= n; i++ {
|
||||
h.Reset()
|
||||
if err := binary.Write(h, binary.BigEndian, int32(i)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err := h.Write(fixedInput.Bytes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output = append(output, h.Sum(nil)...)
|
||||
}
|
||||
|
||||
return output[:bitLen/8], nil
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/open-policy-agent/opa/internal/version"
|
||||
"github.com/open-policy-agent/opa/v1/logging"
|
||||
)
|
||||
|
||||
// Values taken from
|
||||
// https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_GetAuthorizationToken.html
|
||||
const (
|
||||
ecrGetAuthorizationTokenTarget = "AmazonEC2ContainerRegistry_V20150921.GetAuthorizationToken"
|
||||
ecrEndpointFmt = "https://ecr.%s.amazonaws.com/"
|
||||
)
|
||||
|
||||
// ECR is used to request tokens from Elastic Container Registry.
|
||||
type ECR struct {
|
||||
// endpoint returns the region-specifc ECR endpoint.
|
||||
// It can be overridden by tests.
|
||||
endpoint func(region string) string
|
||||
|
||||
// client is used to send authorization tokens requests.
|
||||
client *http.Client
|
||||
|
||||
logger logging.Logger
|
||||
}
|
||||
|
||||
func NewECR(logger logging.Logger) *ECR {
|
||||
return &ECR{
|
||||
endpoint: func(region string) string {
|
||||
return fmt.Sprintf(ecrEndpointFmt, region)
|
||||
},
|
||||
client: &http.Client{},
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// GetAuthorizationToken requests a token that can be used to authenticate image pull requests.
|
||||
func (e *ECR) GetAuthorizationToken(ctx context.Context, creds Credentials, signatureVersion string) (ECRAuthorizationToken, error) {
|
||||
endpoint := e.endpoint(creds.RegionName)
|
||||
body := strings.NewReader("{}")
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, body)
|
||||
if err != nil {
|
||||
return ECRAuthorizationToken{}, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("X-Amz-Target", ecrGetAuthorizationTokenTarget)
|
||||
req.Header.Set("Accept-Encoding", "identity")
|
||||
req.Header.Set("Content-Type", "application/x-amz-json-1.1")
|
||||
req.Header.Set("User-Agent", version.UserAgent)
|
||||
|
||||
e.logger.Debug("Signing ECR authorization token request")
|
||||
|
||||
if err := SignRequest(req, "ecr", creds, time.Now(), signatureVersion); err != nil {
|
||||
return ECRAuthorizationToken{}, fmt.Errorf("failed to sign request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := DoRequestWithClient(req, e.client, "ecr get authorization token", e.logger)
|
||||
if err != nil {
|
||||
return ECRAuthorizationToken{}, err
|
||||
}
|
||||
|
||||
var data struct {
|
||||
AuthorizationData []struct {
|
||||
AuthorizationToken string `json:"authorizationToken"`
|
||||
ExpiresAt json.Number `json:"expiresAt"`
|
||||
} `json:"authorizationData"`
|
||||
}
|
||||
if err := json.Unmarshal(resp, &data); err != nil {
|
||||
return ECRAuthorizationToken{}, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||
}
|
||||
|
||||
if len(data.AuthorizationData) < 1 {
|
||||
return ECRAuthorizationToken{}, errors.New("empty authorization data")
|
||||
}
|
||||
|
||||
// The GetAuthorizationToken request returns a list of tokens for
|
||||
// backwards compatibility reasons. We should only ever get one token back
|
||||
// because we don't define any registryIDs in the request.
|
||||
// See https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_GetAuthorizationToken.html#API_GetAuthorizationToken_ResponseSyntax
|
||||
resultToken := data.AuthorizationData[0]
|
||||
|
||||
expiresAt, err := parseTimestamp(resultToken.ExpiresAt)
|
||||
if err != nil {
|
||||
return ECRAuthorizationToken{}, fmt.Errorf("failed to parse expiresAt: %w", err)
|
||||
}
|
||||
|
||||
return ECRAuthorizationToken{
|
||||
AuthorizationToken: resultToken.AuthorizationToken,
|
||||
ExpiresAt: expiresAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ECRAuthorizationToken can sign requests to AWS ECR.
|
||||
//
|
||||
// It corresponds to data returned by the AWS GetAuthorizationToken API.
|
||||
// See https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_AuthorizationData.html
|
||||
type ECRAuthorizationToken struct {
|
||||
AuthorizationToken string
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
|
||||
// IsValid returns true if the token is set and not expired.
|
||||
// It respects a margin of error for time handling and will mark it as expired early.
|
||||
func (t *ECRAuthorizationToken) IsValid() bool {
|
||||
const tokenExpirationMargin = 5 * time.Minute
|
||||
|
||||
expired := time.Now().Add(tokenExpirationMargin).After(t.ExpiresAt)
|
||||
return t.AuthorizationToken != "" && !expired
|
||||
}
|
||||
|
||||
var millisecondsFloat = new(big.Float).SetInt64(1e3)
|
||||
|
||||
// parseTimestamp parses the AWS format for timestamps.
|
||||
// The time precision is in milliseconds.
|
||||
//
|
||||
// The logic is taken from
|
||||
// https://github.com/aws/aws-sdk-go/blob/41717ba2c04d3fd03f94d09ea984a10899574935/private/protocol/json/jsonutil/unmarshal.go#L294-L302
|
||||
func parseTimestamp(raw json.Number) (time.Time, error) {
|
||||
s := raw.String()
|
||||
|
||||
float, ok := new(big.Float).SetString(s)
|
||||
if !ok {
|
||||
return time.Time{}, fmt.Errorf("not a float: %q", raw)
|
||||
}
|
||||
|
||||
// The float is expected to be in second resolution with millisecond
|
||||
// decimal places.
|
||||
// Multiply by millisecondsFloat to obtain an integer in millisecond
|
||||
// resolution
|
||||
ms, _ := float.Mul(float, millisecondsFloat).Int64()
|
||||
|
||||
// Multiply again to obtain nanosecond resolution for time.Unix
|
||||
ns := ms * 1e6
|
||||
|
||||
t := time.Unix(0, ns).UTC()
|
||||
|
||||
return t, nil
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/open-policy-agent/opa/internal/version"
|
||||
"github.com/open-policy-agent/opa/v1/logging"
|
||||
)
|
||||
|
||||
// Values taken from
|
||||
// https://docs.aws.amazon.com/kms/latest/APIReference/Welcome.html
|
||||
// https://docs.aws.amazon.com/general/latest/gr/kms.html
|
||||
const (
|
||||
kmsSignTarget = "TrentService.Sign"
|
||||
kmsEndpointFmt = "https://kms.%s.amazonaws.com/"
|
||||
)
|
||||
|
||||
// KMS is used to sign payloads using AWS Key Management Service.
|
||||
type KMS struct {
|
||||
// endpoint returns the region-specifc KMS endpoint.
|
||||
// It can be overridden by tests.
|
||||
endpoint func(region string) string
|
||||
|
||||
// client is used to send authorization tokens requests.
|
||||
client *http.Client
|
||||
|
||||
logger logging.Logger
|
||||
}
|
||||
|
||||
func NewKMS(logger logging.Logger) *KMS {
|
||||
return &KMS{
|
||||
endpoint: func(region string) string {
|
||||
return fmt.Sprintf(kmsEndpointFmt, region)
|
||||
},
|
||||
client: &http.Client{},
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func NewKMSWithURLClient(url string, client *http.Client, logger logging.Logger) *KMS {
|
||||
return &KMS{
|
||||
endpoint: func(string) string { return url },
|
||||
client: client,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
type KMSSignRequest struct {
|
||||
KeyID string `json:"KeyId"`
|
||||
Message string `json:"Message"`
|
||||
MessageType string `json:"MessageType"`
|
||||
SigningAlgorithm string `json:"SigningAlgorithm"`
|
||||
}
|
||||
type KMSSignResponse struct {
|
||||
KeyID string `json:"KeyId"`
|
||||
Signature string `json:"Signature"`
|
||||
SigningAlgorithm string `json:"SigningAlgorithm"`
|
||||
}
|
||||
|
||||
// SignDigest signs a digest using KMS.
|
||||
func (k *KMS) SignDigest(ctx context.Context, digest []byte, keyID string, signingAlgorithm string, creds Credentials, signatureVersion string) (string, error) {
|
||||
endpoint := k.endpoint(creds.RegionName)
|
||||
|
||||
kmsRequest := KMSSignRequest{
|
||||
KeyID: keyID,
|
||||
Message: base64.StdEncoding.EncodeToString(digest),
|
||||
MessageType: "DIGEST",
|
||||
SigningAlgorithm: signingAlgorithm,
|
||||
}
|
||||
requestJSONBytes, err := json.Marshal(kmsRequest)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshall request: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBuffer(requestJSONBytes))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("X-Amz-Target", kmsSignTarget)
|
||||
req.Header.Set("Accept-Encoding", "identity")
|
||||
req.Header.Set("Content-Type", "application/x-amz-json-1.1")
|
||||
req.Header.Set("User-Agent", version.UserAgent)
|
||||
|
||||
if err := SignRequest(req, "kms", creds, time.Now(), signatureVersion); err != nil {
|
||||
return "", fmt.Errorf("failed to sign request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := DoRequestWithClient(req, k.client, "kms sign digest", k.logger)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var data KMSSignResponse
|
||||
if err := json.Unmarshal(resp, &data); err != nil {
|
||||
return "", fmt.Errorf("failed to unmarshal response: %w", err)
|
||||
}
|
||||
|
||||
return data.Signature, nil
|
||||
}
|
||||
Generated
Vendored
+206
@@ -0,0 +1,206 @@
|
||||
// Copyright 2022 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 aws
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
v4 "github.com/open-policy-agent/opa/internal/providers/aws/v4"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/ast"
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
func stringFromTerm(t *ast.Term) string {
|
||||
if v, ok := t.Value.(ast.String); ok {
|
||||
return string(v)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Headers that may be mutated before reaching an aws service (eg by a proxy) should be added here to omit them from
|
||||
// the sigv4 canonical request
|
||||
// ref. https://github.com/aws/aws-sdk-go/blob/master/aws/signer/v4/v4.go#L92
|
||||
var awsSigv4IgnoredHeaders = map[string]struct{}{
|
||||
"authorization": {},
|
||||
"user-agent": {},
|
||||
"x-amzn-trace-id": {},
|
||||
}
|
||||
|
||||
type Credentials struct {
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
RegionName string
|
||||
SessionToken string
|
||||
}
|
||||
|
||||
func CredentialsFromObject(v ast.Object) Credentials {
|
||||
var creds Credentials
|
||||
awsAccessKey := v.Get(ast.StringTerm("aws_access_key"))
|
||||
awsSecretKey := v.Get(ast.StringTerm("aws_secret_access_key"))
|
||||
awsRegion := v.Get(ast.StringTerm("aws_region"))
|
||||
awsSessionToken := v.Get(ast.StringTerm("aws_session_token"))
|
||||
|
||||
creds.AccessKey = stringFromTerm(awsAccessKey)
|
||||
creds.SecretKey = stringFromTerm(awsSecretKey)
|
||||
creds.RegionName = stringFromTerm(awsRegion)
|
||||
if awsSessionToken != nil {
|
||||
creds.SessionToken = stringFromTerm(awsSessionToken)
|
||||
}
|
||||
return creds
|
||||
}
|
||||
|
||||
func sha256MAC(message string, key []byte) []byte {
|
||||
mac := hmac.New(sha256.New, key)
|
||||
mac.Write([]byte(message))
|
||||
return mac.Sum(nil)
|
||||
}
|
||||
|
||||
// SignRequest modifies an http.Request to include an AWS V4 signature based on the provided credentials.
|
||||
func SignRequest(req *http.Request, service string, creds Credentials, theTime time.Time, sigVersion string) error {
|
||||
// General ref. https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
|
||||
// S3 ref. https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-auth-using-authorization-header.html
|
||||
// APIGateway ref. https://docs.aws.amazon.com/apigateway/api-reference/signing-requests/
|
||||
|
||||
var body []byte
|
||||
if req.Body == nil {
|
||||
body = []byte("")
|
||||
} else {
|
||||
var err error
|
||||
body, err = io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return errors.New("error getting request body: " + err.Error())
|
||||
}
|
||||
// Since ReadAll consumed the body ReadCloser, we must create a new ReadCloser for the request so that the
|
||||
// subsequent read starts from the beginning
|
||||
req.Body = io.NopCloser(bytes.NewReader(body))
|
||||
}
|
||||
|
||||
now := theTime.UTC()
|
||||
|
||||
if sigVersion == "4a" {
|
||||
signedHeaders := SignV4a(req.Header, req.Method, req.URL, body, service, creds, now)
|
||||
req.Header = signedHeaders
|
||||
} else {
|
||||
authHeader, awsHeaders := SignV4(req.Header, req.Method, req.URL, body, service, creds, now, false)
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
for k, v := range awsHeaders {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SignV4 modifies a map[string][]string of headers to generate an AWS V4 signature + headers based on the config/credentials provided.
|
||||
func SignV4(headers map[string][]string, method string, theURL *url.URL, body []byte, service string,
|
||||
awsCreds Credentials, theTime time.Time, disablePayloadSigning bool) (string, map[string]string) {
|
||||
// General ref. https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
|
||||
// S3 ref. https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-auth-using-authorization-header.html
|
||||
// APIGateway ref. https://docs.aws.amazon.com/apigateway/api-reference/signing-requests/
|
||||
|
||||
now := theTime.UTC()
|
||||
|
||||
contentSha256 := getContentHash(disablePayloadSigning, body)
|
||||
|
||||
// V4 signing has specific ideas of how it wants to see dates/times encoded
|
||||
dateNow := now.Format("20060102")
|
||||
iso8601Now := now.Format("20060102T150405Z")
|
||||
|
||||
awsHeaders := map[string]string{
|
||||
"host": theURL.Host,
|
||||
"x-amz-date": iso8601Now,
|
||||
}
|
||||
|
||||
// s3 and glacier require the extra x-amz-content-sha256 header. other services do not.
|
||||
if service == "s3" || service == "glacier" {
|
||||
awsHeaders[amzContentSha256Key] = contentSha256
|
||||
}
|
||||
|
||||
// the security token header is necessary for ephemeral credentials, e.g. from
|
||||
// the EC2 metadata service
|
||||
if awsCreds.SessionToken != "" {
|
||||
awsHeaders["x-amz-security-token"] = awsCreds.SessionToken
|
||||
}
|
||||
|
||||
headersToSign := map[string][]string{}
|
||||
// sign all of the aws headers.
|
||||
for k, v := range awsHeaders {
|
||||
headersToSign[k] = []string{v}
|
||||
}
|
||||
|
||||
// sign all of the request's headers, except for those in the ignore list
|
||||
for k, v := range headers {
|
||||
lowercaseHeader := strings.ToLower(k)
|
||||
if _, ok := awsSigv4IgnoredHeaders[lowercaseHeader]; !ok {
|
||||
headersToSign[lowercaseHeader] = v
|
||||
}
|
||||
}
|
||||
|
||||
// the "canonical request" is the normalized version of the AWS service access
|
||||
// that we're attempting to perform
|
||||
canonicalReq := method + "\n" // HTTP method
|
||||
canonicalReq += theURL.EscapedPath() + "\n" // URI-escaped path
|
||||
canonicalReq += theURL.RawQuery + "\n" // RAW Query String
|
||||
|
||||
// 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
|
||||
|
||||
// include the list of the signed headers
|
||||
headerList := strings.Join(orderedKeys, ";")
|
||||
canonicalReq += headerList + "\n"
|
||||
canonicalReq += contentSha256
|
||||
|
||||
// the "string to sign" is a time-bounded, scoped request token which
|
||||
// is linked to the "canonical request" by inclusion of its SHA-256 hash
|
||||
strToSign := "AWS4-HMAC-SHA256\n" // V4 signing with SHA-256 HMAC
|
||||
strToSign += iso8601Now + "\n" // ISO 8601 time
|
||||
strToSign += dateNow + "/" + awsCreds.RegionName + "/" + service + "/aws4_request\n" // scoping for signature
|
||||
strToSign += fmt.Sprintf("%x", sha256.Sum256([]byte(canonicalReq))) // SHA-256 of canonical request
|
||||
|
||||
// the "signing key" is generated by repeated HMAC-SHA256 based on the same
|
||||
// scoping that's included in the "string to sign"; but including the secret key
|
||||
// to allow AWS to validate it
|
||||
signingKey := sha256MAC(dateNow, []byte("AWS4"+awsCreds.SecretKey))
|
||||
signingKey = sha256MAC(awsCreds.RegionName, signingKey)
|
||||
signingKey = sha256MAC(service, signingKey)
|
||||
signingKey = sha256MAC("aws4_request", signingKey)
|
||||
|
||||
// the "signature" is finally the "string to sign" signed by the "signing key"
|
||||
signature := sha256MAC(strToSign, signingKey)
|
||||
|
||||
// required format of Authorization header; n.b. the access key corresponding to
|
||||
// the secret key is included here
|
||||
authHeader := "AWS4-HMAC-SHA256 Credential=" + awsCreds.AccessKey + "/" + dateNow
|
||||
authHeader += "/" + awsCreds.RegionName + "/" + service + "/aws4_request,"
|
||||
authHeader += "SignedHeaders=" + headerList + ","
|
||||
authHeader += "Signature=" + hex.EncodeToString(signature)
|
||||
|
||||
return authHeader, awsHeaders
|
||||
}
|
||||
|
||||
// getContentHash returns UNSIGNED-PAYLOAD if payload signing is disabled else will compute sha256 from body
|
||||
func getContentHash(disablePayloadSigning bool, body []byte) string {
|
||||
if disablePayloadSigning {
|
||||
return v4.UnsignedPayload
|
||||
}
|
||||
return fmt.Sprintf("%x", sha256.Sum256(body))
|
||||
}
|
||||
Generated
Vendored
+422
@@ -0,0 +1,422 @@
|
||||
// modified from github.com/aws/aws-sdk-go-v2/internal/v4a@7a32d707af
|
||||
package aws
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdh"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"hash"
|
||||
"io"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
signerCrypto "github.com/open-policy-agent/opa/internal/providers/aws/crypto"
|
||||
v4Internal "github.com/open-policy-agent/opa/internal/providers/aws/v4"
|
||||
)
|
||||
|
||||
const (
|
||||
// AmzRegionSetKey represents the region set header used for sigv4a
|
||||
AmzRegionSetKey = "X-Amz-Region-Set"
|
||||
amzSecurityTokenKey = v4Internal.AmzSecurityTokenKey
|
||||
amzDateKey = v4Internal.AmzDateKey
|
||||
authorizationHeader = "Authorization"
|
||||
amzContentSha256Key = "x-amz-content-sha256"
|
||||
|
||||
signingAlgorithm = "AWS4-ECDSA-P256-SHA256"
|
||||
|
||||
timeFormat = "20060102T150405Z"
|
||||
shortTimeFormat = "20060102"
|
||||
)
|
||||
|
||||
var (
|
||||
p256 elliptic.Curve
|
||||
nMinusTwoP256 *big.Int
|
||||
|
||||
one = new(big.Int).SetInt64(1)
|
||||
|
||||
cache = credsCache{}
|
||||
|
||||
randomSource = rand.Reader
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Ensure the elliptic curve parameters are initialized on package import rather then on first usage
|
||||
p256 = elliptic.P256()
|
||||
|
||||
nMinusTwoP256 = new(big.Int).SetBytes(p256.Params().N.Bytes())
|
||||
nMinusTwoP256 = nMinusTwoP256.Sub(nMinusTwoP256, new(big.Int).SetInt64(2))
|
||||
}
|
||||
|
||||
type credsCache struct {
|
||||
asymmetric atomic.Value
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
// SetRandomSource used for testing to override rand so tests can expect stable output
|
||||
func SetRandomSource(reader io.Reader) {
|
||||
randomSource = reader
|
||||
}
|
||||
|
||||
// deriveKeyFromAccessKeyPair derives a NIST P-256 PrivateKey from the given
|
||||
// IAM AccessKey and SecretKey pair.
|
||||
//
|
||||
// Based on FIPS.186-4 Appendix B.4.2
|
||||
func deriveKeyFromAccessKeyPair(accessKey, secretKey string) (*ecdsa.PrivateKey, error) {
|
||||
params := p256.Params()
|
||||
bitLen := params.BitSize // Testing random candidates does not require an additional 64 bits
|
||||
counter := 0x01
|
||||
|
||||
buffer := make([]byte, 1+len(accessKey)) // 1 byte counter + len(accessKey)
|
||||
kdfContext := bytes.NewBuffer(buffer)
|
||||
|
||||
inputKey := append([]byte("AWS4A"), []byte(secretKey)...)
|
||||
|
||||
d := new(big.Int)
|
||||
for {
|
||||
kdfContext.Reset()
|
||||
kdfContext.WriteString(accessKey)
|
||||
kdfContext.WriteByte(byte(counter))
|
||||
|
||||
key, err := signerCrypto.HMACKeyDerivation(sha256.New, bitLen, inputKey, []byte(signingAlgorithm), kdfContext.Bytes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check key first before calling SetBytes if key is in fact a valid candidate.
|
||||
// This ensures the byte slice is the correct length (32-bytes) to compare in constant-time
|
||||
cmp, err := signerCrypto.ConstantTimeByteCompare(key, nMinusTwoP256.Bytes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cmp == -1 {
|
||||
d.SetBytes(key)
|
||||
break
|
||||
}
|
||||
|
||||
counter++
|
||||
if counter > 0xFF {
|
||||
return nil, errors.New("exhausted single byte external counter")
|
||||
}
|
||||
}
|
||||
d = d.Add(d, one)
|
||||
|
||||
priv := new(ecdsa.PrivateKey)
|
||||
priv.PublicKey.Curve = p256
|
||||
priv.D = d
|
||||
|
||||
dBytes := make([]byte, 32)
|
||||
d.FillBytes(dBytes)
|
||||
|
||||
ecdhPriv, err := ecdh.P256().NewPrivateKey(dBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pubBytes := ecdhPriv.PublicKey().Bytes()
|
||||
|
||||
priv.PublicKey.X = new(big.Int).SetBytes(pubBytes[1:33])
|
||||
priv.PublicKey.Y = new(big.Int).SetBytes(pubBytes[33:])
|
||||
|
||||
return priv, nil
|
||||
}
|
||||
|
||||
// v4aCredentials is Context, ECDSA, and Optional Session Token that can be used
|
||||
// to sign requests using SigV4a
|
||||
type v4aCredentials struct {
|
||||
Context string
|
||||
PrivateKey *ecdsa.PrivateKey
|
||||
SessionToken string
|
||||
}
|
||||
|
||||
// retrievePrivateKey returns credentials suitable for SigV4a signing
|
||||
func retrievePrivateKey(symmetric Credentials) (v4aCredentials, error) {
|
||||
cache.m.Lock()
|
||||
defer cache.m.Unlock()
|
||||
|
||||
// try to get creds from cache
|
||||
v := cache.asymmetric.Load()
|
||||
if v != nil {
|
||||
c := v.(*v4aCredentials)
|
||||
// if the cached Context matches the symmetric AccessKey ID, then use cached value. Otherwise, creds have
|
||||
// changed and we need to derive new asymmetric creds
|
||||
if c != nil && c.Context == symmetric.AccessKey {
|
||||
return *c, nil
|
||||
}
|
||||
}
|
||||
|
||||
privateKey, err := deriveKeyFromAccessKeyPair(symmetric.AccessKey, symmetric.SecretKey)
|
||||
if err != nil {
|
||||
return v4aCredentials{}, errors.New("failed to derive asymmetric key from credentials")
|
||||
}
|
||||
|
||||
creds := v4aCredentials{
|
||||
Context: symmetric.AccessKey,
|
||||
PrivateKey: privateKey,
|
||||
SessionToken: symmetric.SessionToken,
|
||||
}
|
||||
|
||||
// cache derived asymmetric creds so we don't derive new ones until symmetric creds change
|
||||
cache.asymmetric.Store(&creds)
|
||||
|
||||
return creds, nil
|
||||
}
|
||||
|
||||
type httpSigner struct {
|
||||
Request *http.Request
|
||||
ServiceName string
|
||||
RegionSet []string
|
||||
Time time.Time
|
||||
Credentials v4aCredentials
|
||||
|
||||
// PayloadHash is the hex encoded SHA-256 hash of the request payload
|
||||
// If len(PayloadHash) == 0 the signer will attempt to send the request
|
||||
// as an unsigned payload. Note: Unsigned payloads only work for a subset of services.
|
||||
PayloadHash string
|
||||
}
|
||||
|
||||
func (s *httpSigner) setRequiredSigningFields(headers http.Header, _ url.Values) {
|
||||
amzDate := s.Time.Format(timeFormat)
|
||||
|
||||
headers.Set(AmzRegionSetKey, strings.Join(s.RegionSet, ","))
|
||||
headers.Set(amzDateKey, amzDate)
|
||||
if len(s.Credentials.SessionToken) > 0 {
|
||||
headers.Set(amzSecurityTokenKey, s.Credentials.SessionToken)
|
||||
}
|
||||
}
|
||||
|
||||
// Build modifies the Request attribute of the httpSigner, adding an Authorization header
|
||||
func (s *httpSigner) Build() (signedRequest, error) {
|
||||
req := s.Request
|
||||
|
||||
query := req.URL.Query()
|
||||
headers := req.Header
|
||||
|
||||
// seemingly required by S3/MRAP -- 403 Forbidden otherwise
|
||||
headers.Set("host", req.URL.Host)
|
||||
headers.Set(amzContentSha256Key, s.PayloadHash)
|
||||
|
||||
s.setRequiredSigningFields(headers, query)
|
||||
|
||||
// Sort Each Query Key's Values
|
||||
for key := range query {
|
||||
sort.Strings(query[key])
|
||||
}
|
||||
|
||||
v4Internal.SanitizeHostForHeader(req)
|
||||
|
||||
credentialScope := s.buildCredentialScope()
|
||||
credentialStr := s.Credentials.Context + "/" + credentialScope
|
||||
|
||||
unsignedHeaders := headers
|
||||
|
||||
host := req.URL.Host
|
||||
if len(req.Host) > 0 {
|
||||
host = req.Host
|
||||
}
|
||||
|
||||
signedHeaders, signedHeadersStr, canonicalHeaderStr := s.buildCanonicalHeaders(host, v4Internal.IgnoredHeaders, unsignedHeaders, s.Request.ContentLength)
|
||||
|
||||
rawQuery := strings.ReplaceAll(query.Encode(), "+", "%20")
|
||||
|
||||
canonicalURI := v4Internal.GetURIPath(req.URL)
|
||||
|
||||
canonicalString := s.buildCanonicalString(
|
||||
req.Method,
|
||||
canonicalURI,
|
||||
rawQuery,
|
||||
signedHeadersStr,
|
||||
canonicalHeaderStr,
|
||||
)
|
||||
|
||||
strToSign := s.buildStringToSign(credentialScope, canonicalString)
|
||||
signingSignature, err := s.buildSignature(strToSign)
|
||||
if err != nil {
|
||||
return signedRequest{}, err
|
||||
}
|
||||
|
||||
headers[authorizationHeader] = append(headers[authorizationHeader][:0], buildAuthorizationHeader(credentialStr, signedHeadersStr, signingSignature))
|
||||
|
||||
req.URL.RawQuery = rawQuery
|
||||
|
||||
return signedRequest{
|
||||
Request: req,
|
||||
SignedHeaders: signedHeaders,
|
||||
CanonicalString: canonicalString,
|
||||
StringToSign: strToSign,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *httpSigner) buildCredentialScope() string {
|
||||
return strings.Join([]string{
|
||||
s.Time.Format(shortTimeFormat),
|
||||
s.ServiceName,
|
||||
"aws4_request",
|
||||
}, "/")
|
||||
|
||||
}
|
||||
|
||||
func buildAuthorizationHeader(credentialStr, signedHeadersStr, signingSignature string) string {
|
||||
const credential = "Credential="
|
||||
const signedHeaders = "SignedHeaders="
|
||||
const signature = "Signature="
|
||||
const commaSpace = ", "
|
||||
|
||||
var parts strings.Builder
|
||||
parts.Grow(len(signingAlgorithm) + 1 +
|
||||
len(credential) + len(credentialStr) + len(commaSpace) +
|
||||
len(signedHeaders) + len(signedHeadersStr) + len(commaSpace) +
|
||||
len(signature) + len(signingSignature),
|
||||
)
|
||||
parts.WriteString(signingAlgorithm)
|
||||
parts.WriteRune(' ')
|
||||
parts.WriteString(credential)
|
||||
parts.WriteString(credentialStr)
|
||||
parts.WriteString(commaSpace)
|
||||
parts.WriteString(signedHeaders)
|
||||
parts.WriteString(signedHeadersStr)
|
||||
parts.WriteString(commaSpace)
|
||||
parts.WriteString(signature)
|
||||
parts.WriteString(signingSignature)
|
||||
return parts.String()
|
||||
}
|
||||
|
||||
func (*httpSigner) buildCanonicalHeaders(host string, rule v4Internal.Rule, header http.Header, length int64) (signed http.Header, signedHeaders, canonicalHeadersStr string) {
|
||||
signed = make(http.Header)
|
||||
|
||||
const hostHeader = "host"
|
||||
headers := make([]string, 0)
|
||||
|
||||
if length > 0 {
|
||||
const contentLengthHeader = "content-length"
|
||||
headers = append(headers, contentLengthHeader)
|
||||
signed[contentLengthHeader] = append(signed[contentLengthHeader], strconv.FormatInt(length, 10))
|
||||
}
|
||||
|
||||
for k, v := range header {
|
||||
if !rule.IsValid(k) {
|
||||
continue // ignored header
|
||||
}
|
||||
|
||||
lowerCaseKey := strings.ToLower(k)
|
||||
if _, ok := signed[lowerCaseKey]; ok {
|
||||
// include additional values
|
||||
signed[lowerCaseKey] = append(signed[lowerCaseKey], v...)
|
||||
continue
|
||||
}
|
||||
|
||||
headers = append(headers, lowerCaseKey)
|
||||
signed[lowerCaseKey] = v
|
||||
}
|
||||
sort.Strings(headers)
|
||||
|
||||
signedHeaders = strings.Join(headers, ";")
|
||||
|
||||
var canonicalHeaders strings.Builder
|
||||
n := len(headers)
|
||||
const colon = ':'
|
||||
for i := range n {
|
||||
if headers[i] == hostHeader {
|
||||
canonicalHeaders.WriteString(hostHeader)
|
||||
canonicalHeaders.WriteRune(colon)
|
||||
canonicalHeaders.WriteString(v4Internal.StripExcessSpaces(host))
|
||||
} else {
|
||||
canonicalHeaders.WriteString(headers[i])
|
||||
canonicalHeaders.WriteRune(colon)
|
||||
// Trim out leading, trailing, and dedup inner spaces from signed header values.
|
||||
values := signed[headers[i]]
|
||||
for j, v := range values {
|
||||
cleanedValue := strings.TrimSpace(v4Internal.StripExcessSpaces(v))
|
||||
canonicalHeaders.WriteString(cleanedValue)
|
||||
if j < len(values)-1 {
|
||||
canonicalHeaders.WriteRune(',')
|
||||
}
|
||||
}
|
||||
}
|
||||
canonicalHeaders.WriteRune('\n')
|
||||
}
|
||||
canonicalHeadersStr = canonicalHeaders.String()
|
||||
|
||||
return signed, signedHeaders, canonicalHeadersStr
|
||||
}
|
||||
|
||||
func (s *httpSigner) buildCanonicalString(method, uri, query, signedHeaders, canonicalHeaders string) string {
|
||||
return strings.Join([]string{
|
||||
method,
|
||||
uri,
|
||||
query,
|
||||
canonicalHeaders,
|
||||
signedHeaders,
|
||||
s.PayloadHash,
|
||||
}, "\n")
|
||||
}
|
||||
|
||||
func (s *httpSigner) buildStringToSign(credentialScope, canonicalRequestString string) string {
|
||||
return strings.Join([]string{
|
||||
signingAlgorithm,
|
||||
s.Time.Format(timeFormat),
|
||||
credentialScope,
|
||||
hex.EncodeToString(makeHash(sha256.New(), []byte(canonicalRequestString))),
|
||||
}, "\n")
|
||||
}
|
||||
|
||||
func makeHash(hash hash.Hash, b []byte) []byte {
|
||||
hash.Reset()
|
||||
hash.Write(b)
|
||||
return hash.Sum(nil)
|
||||
}
|
||||
|
||||
func (s *httpSigner) buildSignature(strToSign string) (string, error) {
|
||||
sig, err := s.Credentials.PrivateKey.Sign(randomSource, makeHash(sha256.New(), []byte(strToSign)), crypto.SHA256)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(sig), nil
|
||||
}
|
||||
|
||||
type signedRequest struct {
|
||||
Request *http.Request
|
||||
SignedHeaders http.Header
|
||||
CanonicalString string
|
||||
StringToSign string
|
||||
}
|
||||
|
||||
// SignV4a returns a map[string][]string of headers, including an added AWS V4a signature based on the config/credentials provided.
|
||||
func SignV4a(headers map[string][]string, method string, theURL *url.URL, body []byte, service string, awsCreds Credentials, theTime time.Time) map[string][]string {
|
||||
contentSha256 := getContentHash(false, body)
|
||||
key, err := retrievePrivateKey(awsCreds)
|
||||
if err != nil {
|
||||
return map[string][]string{}
|
||||
}
|
||||
|
||||
bodyReader := bytes.NewReader(body)
|
||||
req, _ := http.NewRequest(method, theURL.String(), bodyReader)
|
||||
req.Header = headers
|
||||
|
||||
signer := &httpSigner{
|
||||
Request: req,
|
||||
PayloadHash: contentSha256,
|
||||
ServiceName: service,
|
||||
RegionSet: []string{"*"},
|
||||
Credentials: key,
|
||||
Time: theTime,
|
||||
}
|
||||
|
||||
_, err = signer.Build()
|
||||
if err != nil {
|
||||
return map[string][]string{}
|
||||
}
|
||||
|
||||
return req.Header
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/logging"
|
||||
)
|
||||
|
||||
// DoRequestWithClient is a convenience function to get the body of an HTTP response with
|
||||
// appropriate error-handling boilerplate and logging.
|
||||
func DoRequestWithClient(req *http.Request, client *http.Client, desc string, logger logging.Logger) ([]byte, error) {
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
// some kind of catastrophe talking to the service
|
||||
return nil, errors.New(desc + " HTTP request failed: " + err.Error())
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
logger.WithFields(map[string]any{
|
||||
"url": req.URL.String(),
|
||||
"status": resp.Status,
|
||||
"headers": resp.Header,
|
||||
}).Debug("Received response from " + desc + " service.")
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
// deal with problems reading the body, whatever those might be
|
||||
return nil, errors.New(desc + " HTTP response body could not be read: " + err.Error())
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
logger.Debug("Error response with response body: %s", body)
|
||||
// could be 404 for role that's not available, but cover all the bases
|
||||
return nil, errors.New(desc + " HTTP request returned unexpected status: " + resp.Status)
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
Generated
Vendored
+36
@@ -0,0 +1,36 @@
|
||||
package v4
|
||||
|
||||
const (
|
||||
// EmptyStringSHA256 is the hex encoded sha256 value of an empty string
|
||||
EmptyStringSHA256 = `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`
|
||||
|
||||
// UnsignedPayload indicates that the request payload body is unsigned
|
||||
UnsignedPayload = "UNSIGNED-PAYLOAD"
|
||||
|
||||
// AmzAlgorithmKey indicates the signing algorithm
|
||||
AmzAlgorithmKey = "X-Amz-Algorithm"
|
||||
|
||||
// AmzSecurityTokenKey indicates the security token to be used with temporary credentials
|
||||
AmzSecurityTokenKey = "X-Amz-Security-Token"
|
||||
|
||||
// AmzDateKey is the UTC timestamp for the request in the format YYYYMMDD'T'HHMMSS'Z'
|
||||
AmzDateKey = "X-Amz-Date"
|
||||
|
||||
// AmzCredentialKey is the access key ID and credential scope
|
||||
AmzCredentialKey = "X-Amz-Credential"
|
||||
|
||||
// AmzSignedHeadersKey is the set of headers signed for the request
|
||||
AmzSignedHeadersKey = "X-Amz-SignedHeaders"
|
||||
|
||||
// AmzSignatureKey is the query parameter to store the SigV4 signature
|
||||
AmzSignatureKey = "X-Amz-Signature"
|
||||
|
||||
// TimeFormat is the time format to be used in the X-Amz-Date header or query parameter
|
||||
TimeFormat = "20060102T150405Z"
|
||||
|
||||
// ShortTimeFormat is the shorten time format used in the credential scope
|
||||
ShortTimeFormat = "20060102"
|
||||
|
||||
// ContentSHAKey is the SHA256 of request body
|
||||
ContentSHAKey = "X-Amz-Content-Sha256"
|
||||
)
|
||||
Generated
Vendored
+87
@@ -0,0 +1,87 @@
|
||||
package v4
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Rules houses a set of Rule needed for validation of a
|
||||
// string value
|
||||
type Rules []Rule
|
||||
|
||||
// Rule interface allows for more flexible rules and just simply
|
||||
// checks whether or not a value adheres to that Rule
|
||||
type Rule interface {
|
||||
IsValid(value string) bool
|
||||
}
|
||||
|
||||
// IsValid will iterate through all rules and see if any rules
|
||||
// apply to the value and supports nested rules
|
||||
func (r Rules) IsValid(value string) bool {
|
||||
for _, rule := range r {
|
||||
if rule.IsValid(value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MapRule generic Rule for maps
|
||||
type MapRule map[string]struct{}
|
||||
|
||||
// IsValid for the map Rule satisfies whether it exists in the map
|
||||
func (m MapRule) IsValid(value string) bool {
|
||||
_, ok := m[value]
|
||||
return ok
|
||||
}
|
||||
|
||||
// AllowList is a generic Rule for whitelisting
|
||||
type AllowList struct {
|
||||
Rule
|
||||
}
|
||||
|
||||
// IsValid for AllowList checks if the value is within the AllowList
|
||||
func (w AllowList) IsValid(value string) bool {
|
||||
return w.Rule.IsValid(value)
|
||||
}
|
||||
|
||||
// DenyList is a generic Rule for blacklisting
|
||||
type DenyList struct {
|
||||
Rule
|
||||
}
|
||||
|
||||
// IsValid for AllowList checks if the value is within the AllowList
|
||||
func (b DenyList) IsValid(value string) bool {
|
||||
return !b.Rule.IsValid(value)
|
||||
}
|
||||
|
||||
// Patterns is a list of strings to match against
|
||||
type Patterns []string
|
||||
|
||||
// FORK: copied from aws-sdk-go-v2/internal/strings
|
||||
func hasPrefixFold(s, prefix string) bool {
|
||||
return len(s) >= len(prefix) && strings.EqualFold(s[0:len(prefix)], prefix)
|
||||
}
|
||||
|
||||
// IsValid for Patterns checks each pattern and returns if a match has
|
||||
// been found
|
||||
func (p Patterns) IsValid(value string) bool {
|
||||
for _, pattern := range p {
|
||||
if hasPrefixFold(value, pattern) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// InclusiveRules rules allow for rules to depend on one another
|
||||
type InclusiveRules []Rule
|
||||
|
||||
// IsValid will return true if all rules are true
|
||||
func (r InclusiveRules) IsValid(value string) bool {
|
||||
for _, rule := range r {
|
||||
if !rule.IsValid(value) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
Generated
Vendored
+67
@@ -0,0 +1,67 @@
|
||||
package v4
|
||||
|
||||
// IgnoredHeaders is a list of headers that are ignored during signing
|
||||
var IgnoredHeaders = Rules{
|
||||
DenyList{
|
||||
MapRule{
|
||||
"Authorization": struct{}{},
|
||||
"User-Agent": struct{}{},
|
||||
"X-Amzn-Trace-Id": struct{}{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// RequiredSignedHeaders is a whitelist for Build canonical headers.
|
||||
var RequiredSignedHeaders = Rules{
|
||||
AllowList{
|
||||
MapRule{
|
||||
"Cache-Control": struct{}{},
|
||||
"Content-Disposition": struct{}{},
|
||||
"Content-Encoding": struct{}{},
|
||||
"Content-Language": struct{}{},
|
||||
"Content-Md5": struct{}{},
|
||||
"Content-Type": struct{}{},
|
||||
"Expires": struct{}{},
|
||||
"If-Match": struct{}{},
|
||||
"If-Modified-Since": struct{}{},
|
||||
"If-None-Match": struct{}{},
|
||||
"If-Unmodified-Since": struct{}{},
|
||||
"Range": struct{}{},
|
||||
"X-Amz-Acl": struct{}{},
|
||||
"X-Amz-Copy-Source": struct{}{},
|
||||
"X-Amz-Copy-Source-If-Match": struct{}{},
|
||||
"X-Amz-Copy-Source-If-Modified-Since": struct{}{},
|
||||
"X-Amz-Copy-Source-If-None-Match": struct{}{},
|
||||
"X-Amz-Copy-Source-If-Unmodified-Since": struct{}{},
|
||||
"X-Amz-Copy-Source-Range": struct{}{},
|
||||
"X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm": struct{}{},
|
||||
"X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key": struct{}{},
|
||||
"X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-Md5": struct{}{},
|
||||
"X-Amz-Grant-Full-control": struct{}{},
|
||||
"X-Amz-Grant-Read": struct{}{},
|
||||
"X-Amz-Grant-Read-Acp": struct{}{},
|
||||
"X-Amz-Grant-Write": struct{}{},
|
||||
"X-Amz-Grant-Write-Acp": struct{}{},
|
||||
"X-Amz-Metadata-Directive": struct{}{},
|
||||
"X-Amz-Mfa": struct{}{},
|
||||
"X-Amz-Request-Payer": struct{}{},
|
||||
"X-Amz-Server-Side-Encryption": struct{}{},
|
||||
"X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id": struct{}{},
|
||||
"X-Amz-Server-Side-Encryption-Customer-Algorithm": struct{}{},
|
||||
"X-Amz-Server-Side-Encryption-Customer-Key": struct{}{},
|
||||
"X-Amz-Server-Side-Encryption-Customer-Key-Md5": struct{}{},
|
||||
"X-Amz-Storage-Class": struct{}{},
|
||||
"X-Amz-Website-Redirect-Location": struct{}{},
|
||||
"X-Amz-Content-Sha256": struct{}{},
|
||||
"X-Amz-Tagging": struct{}{},
|
||||
},
|
||||
},
|
||||
Patterns{"X-Amz-Meta-"},
|
||||
}
|
||||
|
||||
// AllowedQueryHoisting is a whitelist for Build query headers. The boolean value
|
||||
// represents whether or not it is a pattern.
|
||||
var AllowedQueryHoisting = InclusiveRules{
|
||||
DenyList{RequiredSignedHeaders},
|
||||
Patterns{"X-Amz-"},
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package v4
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SanitizeHostForHeader removes default port from host and updates request.Host
|
||||
func SanitizeHostForHeader(r *http.Request) {
|
||||
host := getHost(r)
|
||||
port := portOnly(host)
|
||||
if port != "" && isDefaultPort(r.URL.Scheme, port) {
|
||||
r.Host = stripPort(host)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns host from request
|
||||
func getHost(r *http.Request) string {
|
||||
if r.Host != "" {
|
||||
return r.Host
|
||||
}
|
||||
|
||||
return r.URL.Host
|
||||
}
|
||||
|
||||
// Hostname returns u.Host, without any port number.
|
||||
//
|
||||
// If Host is an IPv6 literal with a port number, Hostname returns the
|
||||
// IPv6 literal without the square brackets. IPv6 literals may include
|
||||
// a zone identifier.
|
||||
//
|
||||
// Copied from the Go 1.8 standard library (net/url)
|
||||
func stripPort(hostport string) string {
|
||||
colon := strings.IndexByte(hostport, ':')
|
||||
if colon == -1 {
|
||||
return hostport
|
||||
}
|
||||
if i := strings.IndexByte(hostport, ']'); i != -1 {
|
||||
return strings.TrimPrefix(hostport[:i], "[")
|
||||
}
|
||||
return hostport[:colon]
|
||||
}
|
||||
|
||||
// Port returns the port part of u.Host, without the leading colon.
|
||||
// If u.Host doesn't contain a port, Port returns an empty string.
|
||||
//
|
||||
// Copied from the Go 1.8 standard library (net/url)
|
||||
func portOnly(hostport string) string {
|
||||
colon := strings.IndexByte(hostport, ':')
|
||||
if colon == -1 {
|
||||
return ""
|
||||
}
|
||||
if i := strings.Index(hostport, "]:"); i != -1 {
|
||||
return hostport[i+len("]:"):]
|
||||
}
|
||||
if strings.Contains(hostport, "]") {
|
||||
return ""
|
||||
}
|
||||
return hostport[colon+len(":"):]
|
||||
}
|
||||
|
||||
// Returns true if the specified URI is using the standard port
|
||||
// (i.e. port 80 for HTTP URIs or 443 for HTTPS URIs)
|
||||
func isDefaultPort(scheme, port string) bool {
|
||||
if port == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
lowerCaseScheme := strings.ToLower(scheme)
|
||||
if (lowerCaseScheme == "http" && port == "80") || (lowerCaseScheme == "https" && port == "443") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package v4
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const doubleSpace = " "
|
||||
|
||||
// StripExcessSpaces will rewrite the passed in slice's string values to not
|
||||
// contain multiple side-by-side spaces.
|
||||
func StripExcessSpaces(str string) string {
|
||||
var j, k, l, m, spaces int
|
||||
|
||||
// Trim leading and trailing spaces
|
||||
str = strings.Trim(str, " ")
|
||||
|
||||
// Strip multiple spaces.
|
||||
j = strings.Index(str, doubleSpace)
|
||||
if j < 0 {
|
||||
return str
|
||||
}
|
||||
|
||||
buf := []byte(str)
|
||||
for k, m, l = j, j, len(buf); k < l; k++ {
|
||||
if buf[k] == ' ' {
|
||||
if spaces == 0 {
|
||||
// First space.
|
||||
buf[m] = buf[k]
|
||||
m++
|
||||
}
|
||||
spaces++
|
||||
} else {
|
||||
// End of multiple spaces.
|
||||
spaces = 0
|
||||
buf[m] = buf[k]
|
||||
m++
|
||||
}
|
||||
}
|
||||
|
||||
return string(buf[:m])
|
||||
}
|
||||
|
||||
// GetURIPath returns the escaped URI component from the provided URL
|
||||
func GetURIPath(u *url.URL) string {
|
||||
var uri string
|
||||
|
||||
if len(u.Opaque) > 0 {
|
||||
uri = "/" + strings.Join(strings.Split(u.Opaque, "/")[3:], "/")
|
||||
} else {
|
||||
uri = u.EscapedPath()
|
||||
}
|
||||
|
||||
if len(uri) == 0 {
|
||||
uri = "/"
|
||||
}
|
||||
|
||||
return uri
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// Copyright 2020 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package ref implements internal helpers for references
|
||||
package ref
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/ast"
|
||||
"github.com/open-policy-agent/opa/v1/storage"
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// ParseDataPath returns a ref from the slash separated path s rooted at data.
|
||||
// All path segments are treated as identifier strings.
|
||||
func ParseDataPath(s string) (ast.Ref, error) {
|
||||
path, ok := storage.ParsePath(util.WithPrefix(s, "/"))
|
||||
if !ok {
|
||||
return nil, errors.New("invalid path")
|
||||
}
|
||||
|
||||
return path.Ref(ast.DefaultRootDocument), nil
|
||||
}
|
||||
|
||||
// ArrayPath will take an ast.Array and build an ast.Ref using the ast.Terms in the Array
|
||||
func ArrayPath(a *ast.Array) ast.Ref {
|
||||
ref := make(ast.Ref, 0, a.Len())
|
||||
|
||||
a.Foreach(func(term *ast.Term) {
|
||||
ref = append(ref, term)
|
||||
})
|
||||
|
||||
return ref
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
// Copyright 2021 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 opa
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// ErrEngineNotFound is returned by LookupEngine if no wasm engine was
|
||||
// registered by that name.
|
||||
var ErrEngineNotFound error = &errEngineNotFound{}
|
||||
|
||||
type errEngineNotFound struct{}
|
||||
|
||||
func (*errEngineNotFound) Error() string { return "engine not found" }
|
||||
func (*errEngineNotFound) Lines() []string {
|
||||
return []string{
|
||||
`WebAssembly runtime not supported in this build.`,
|
||||
`----------------------------------------------------------------------------------`,
|
||||
`Please download an OPA binary with Wasm enabled from`,
|
||||
`https://www.openpolicyagent.org/docs/latest/#running-opa`,
|
||||
`or build it yourself (with Wasm enabled).`,
|
||||
`----------------------------------------------------------------------------------`,
|
||||
}
|
||||
}
|
||||
|
||||
// Engine repesents a factory for instances of EvalEngine implementations
|
||||
type Engine interface {
|
||||
New() EvalEngine
|
||||
}
|
||||
|
||||
// EvalEngine is the interface implemented by an engine used to eval a policy
|
||||
type EvalEngine interface {
|
||||
Init() (EvalEngine, error)
|
||||
Entrypoints(context.Context) (map[string]int32, error)
|
||||
WithPolicyBytes([]byte) EvalEngine
|
||||
WithDataJSON(any) EvalEngine
|
||||
Eval(context.Context, EvalOpts) (*Result, error)
|
||||
SetData(context.Context, any) error
|
||||
SetDataPath(context.Context, []string, any) error
|
||||
RemoveDataPath(context.Context, []string) error
|
||||
Close()
|
||||
}
|
||||
|
||||
var engines = map[string]Engine{}
|
||||
|
||||
// RegisterEngine registers an evaluation engine by its target name.
|
||||
// Note that the "rego" target is always available.
|
||||
func RegisterEngine(name string, e Engine) {
|
||||
if engines[name] != nil {
|
||||
panic("duplicate engine registration")
|
||||
}
|
||||
engines[name] = e
|
||||
}
|
||||
|
||||
// LookupEngine allows retrieving an engine registered by name
|
||||
func LookupEngine(name string) (Engine, error) {
|
||||
e, ok := engines[name]
|
||||
if !ok {
|
||||
return nil, ErrEngineNotFound
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package opa
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/ast"
|
||||
"github.com/open-policy-agent/opa/v1/metrics"
|
||||
"github.com/open-policy-agent/opa/v1/topdown/builtins"
|
||||
"github.com/open-policy-agent/opa/v1/topdown/cache"
|
||||
"github.com/open-policy-agent/opa/v1/topdown/print"
|
||||
)
|
||||
|
||||
// Result holds the evaluation result.
|
||||
type Result struct {
|
||||
Result []byte
|
||||
}
|
||||
|
||||
// EvalOpts define options for performing an evaluation.
|
||||
type EvalOpts struct {
|
||||
Input *any
|
||||
Metrics metrics.Metrics
|
||||
Entrypoint int32
|
||||
Time time.Time
|
||||
Seed io.Reader
|
||||
InterQueryBuiltinCache cache.InterQueryCache
|
||||
InterQueryBuiltinValueCache cache.InterQueryValueCache
|
||||
NDBuiltinCache builtins.NDBCache
|
||||
PrintHook print.Hook
|
||||
Capabilities *ast.Capabilities
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
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:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) 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
|
||||
|
||||
(d) 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 [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
+247
@@ -0,0 +1,247 @@
|
||||
// Copyright 2013-2015 CoreOS, Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
// Semantic Versions http://semver.org
|
||||
|
||||
// This file was originally vendored from:
|
||||
// https://github.com/coreos/go-semver/tree/e214231b295a8ea9479f11b70b35d5acf3556d9b/semver
|
||||
// There isn't a single line left from the original source today, but being generous about
|
||||
// attribution won't hurt.
|
||||
package semver
|
||||
|
||||
import (
|
||||
"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 string `json:"PreRelease,omitempty"`
|
||||
Metadata string `json:"Metadata,omitempty"`
|
||||
}
|
||||
|
||||
// Parse constructs new semver Version from version string.
|
||||
func Parse(version string) (v Version, err error) {
|
||||
version = strings.TrimPrefix(version, "v")
|
||||
|
||||
version, v.Metadata = cut(version, '+')
|
||||
if v.Metadata != "" && !reMetaIdentifier.MatchString(v.Metadata) {
|
||||
return v, fmt.Errorf("invalid metadata identifier: %s", v.Metadata)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Compare compares two semver strings.
|
||||
func Compare(a, b string) int {
|
||||
aV, err := Parse(a)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
bV, err := Parse(b)
|
||||
if err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
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, ""
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
// Copyright 2021 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 strings contains helpers to perform string manipulation
|
||||
package strings
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/open-policy-agent/opa/internal/lcss"
|
||||
)
|
||||
|
||||
// TruncateFilePaths truncates the given file paths to conform to the given
|
||||
// "ideal" width and returns the shortened paths by replacing the middle parts of paths
|
||||
// with "...", ex: bundle1/.../a/b/policy.rego
|
||||
func TruncateFilePaths(maxIdealWidth, maxWidth int, path ...string) (map[string]string, int) {
|
||||
canShorten := make([][]byte, 0, len(path))
|
||||
|
||||
for _, p := range path {
|
||||
canShorten = append(canShorten, []byte(getPathFromFirstSeparator(p)))
|
||||
}
|
||||
|
||||
// Find the longest common path segment
|
||||
var lcs string
|
||||
if len(canShorten) > 1 {
|
||||
lcs = string(lcss.LongestCommonSubstring(canShorten...))
|
||||
} else {
|
||||
lcs = string(canShorten[0])
|
||||
}
|
||||
|
||||
// Don't just swap in the full LCSS, trim it down to be the least amount of
|
||||
// characters to reach our "ideal" width boundary giving as much
|
||||
// detail as possible without going too long.
|
||||
diff := maxIdealWidth - (maxWidth - len(lcs) + 3)
|
||||
if diff > 0 {
|
||||
if diff > len(lcs) {
|
||||
lcs = ""
|
||||
} else {
|
||||
// Favor data on the right hand side of the path
|
||||
lcs = lcs[:len(lcs)-diff]
|
||||
}
|
||||
}
|
||||
|
||||
result := map[string]string{}
|
||||
for _, p := range path {
|
||||
result[p] = p
|
||||
}
|
||||
|
||||
longestLocation := maxWidth
|
||||
|
||||
// Swap in "..." for the longest common path, but if it makes things better
|
||||
if len(lcs) > 3 {
|
||||
for path := range result {
|
||||
result[path] = strings.Replace(path, lcs, "...", 1)
|
||||
}
|
||||
|
||||
// Drop the overall length down to match our substitution
|
||||
longestLocation -= (len(lcs) - 3)
|
||||
}
|
||||
|
||||
return result, longestLocation
|
||||
}
|
||||
|
||||
func Truncate(str string, maxWidth int) string {
|
||||
if len(str) <= maxWidth {
|
||||
return str
|
||||
}
|
||||
|
||||
return str[:maxWidth-3] + "..."
|
||||
}
|
||||
|
||||
func getPathFromFirstSeparator(path string) string {
|
||||
s := filepath.Dir(path)
|
||||
s = strings.TrimPrefix(s, string(filepath.Separator))
|
||||
firstSlash := strings.IndexRune(s, filepath.Separator)
|
||||
if firstSlash > 0 {
|
||||
return s[firstSlash+1:]
|
||||
}
|
||||
return s
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
// Copyright 2017 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 uuid
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
BILLION = 1000000000
|
||||
)
|
||||
|
||||
// New Create a version 4 random UUID
|
||||
func New(r io.Reader) (string, error) {
|
||||
bs := make([]byte, 16)
|
||||
n, err := io.ReadFull(r, bs)
|
||||
if n != len(bs) || err != nil {
|
||||
return "", err
|
||||
}
|
||||
bs[8] = bs[8]&^0xc0 | 0x80
|
||||
bs[6] = bs[6]&^0xf0 | 0x40
|
||||
return fmt.Sprintf("%x-%x-%x-%x-%x", bs[0:4], bs[4:6], bs[6:8], bs[8:10], bs[10:]), nil
|
||||
}
|
||||
|
||||
// Parse will use the google/uuid library to parse the string into a uuid
|
||||
// if parsing fails, it will return an empty map. It will fill the map
|
||||
// with some decoded values with fillMap
|
||||
// ref: https://datatracker.ietf.org/doc/html/rfc4122
|
||||
func Parse(s string) (map[string]any, error) {
|
||||
uuid, err := uuid.Parse(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make(map[string]any, getVersionLen(int(uuid.Version())))
|
||||
fillMap(out, uuid)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Fills the map with values from the uuid. Version and variant for every version.
|
||||
// Version 1-2 has decodable values that could be of use, version 4 is random,
|
||||
// and version 3,5 is not feasible to extract data. Generated with either MD5 or SHA1 hash
|
||||
// ref: https://datatracker.ietf.org/doc/html/rfc4122 about creation of UUIDs
|
||||
func fillMap(m map[string]any, u uuid.UUID) {
|
||||
m["version"] = int(u.Version())
|
||||
m["variant"] = u.Variant().String()
|
||||
switch version := m["version"]; version {
|
||||
case 1, 2:
|
||||
m["time"] = nanoUnix(u.Time())
|
||||
m["nodeid"] = byteDecimalToHexMAC(u.NodeID(), "-")
|
||||
m["macvariables"] = macVars(u.NodeID()[0])
|
||||
m["clocksequence"] = u.ClockSequence()
|
||||
if version == 2 {
|
||||
m["id"] = int(u.ID())
|
||||
m["domain"] = u.Domain().String()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// macVars will take the first byte of a MAC-address and check for the
|
||||
// local/global bit and check for the unicast/multicast bit of the byte,
|
||||
// and return a string with this info.
|
||||
// ref: https://datatracker.ietf.org/doc/html/rfc7042#section-2.1
|
||||
func macVars(inpb byte) string {
|
||||
switch {
|
||||
case inpb&byte(0b11) == byte(0b11):
|
||||
return "local:multicast"
|
||||
case inpb&byte(0b01) == byte(0b01):
|
||||
return "global:multicast"
|
||||
case inpb&byte(0b10) == byte(0b10):
|
||||
return "local:unicast"
|
||||
}
|
||||
return "global:unicast"
|
||||
}
|
||||
|
||||
// loops through the byte array to convert all bytes to hexes.
|
||||
// It will also put the separator between every other to make it human-readable
|
||||
func byteDecimalToHexMAC(bytes []byte, sep string) string {
|
||||
hexs := strings.Builder{}
|
||||
l := len(bytes)
|
||||
hexs.Grow((l * 3) - 1) // 1 byte -> 2 hexes + 1 separator (if one char)
|
||||
|
||||
for i, b := range bytes {
|
||||
fmt.Fprintf(&hexs, "%02x", b)
|
||||
if i < l-1 {
|
||||
hexs.WriteString(sep)
|
||||
}
|
||||
}
|
||||
|
||||
return hexs.String()
|
||||
}
|
||||
|
||||
// nanoUnix Converts the uuids encoded time into unix represented time in nanoseconds
|
||||
func nanoUnix(t uuid.Time) int64 {
|
||||
unixsec, unixnsec := t.UnixTime()
|
||||
return unixsec*BILLION + unixnsec
|
||||
}
|
||||
|
||||
// Helper function to make map with length based on version of uuid
|
||||
// Most are 2 in length (version, variant), but version 1 and 2 have more.
|
||||
func getVersionLen(version int) int {
|
||||
switch version {
|
||||
case 1:
|
||||
return 5
|
||||
case 2:
|
||||
return 7
|
||||
default:
|
||||
return 2
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// Copyright 2019 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 version implements helper functions for the stored version.
|
||||
package version
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/storage"
|
||||
"github.com/open-policy-agent/opa/v1/version"
|
||||
)
|
||||
|
||||
var versionPath = storage.MustParsePath("/system/version")
|
||||
|
||||
// Write the build version information into storage. This makes the
|
||||
// version information available to the REPL and the HTTP server.
|
||||
func Write(ctx context.Context, store storage.Store, txn storage.Transaction) error {
|
||||
|
||||
if err := storage.MakeDir(ctx, store, txn, versionPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return store.Write(ctx, txn, storage.AddOp, versionPath, map[string]any{
|
||||
"version": version.Version,
|
||||
"build_commit": version.Vcs,
|
||||
"build_timestamp": version.Timestamp,
|
||||
"build_hostname": version.Hostname,
|
||||
})
|
||||
}
|
||||
|
||||
// UserAgent defines the current OPA instances User-Agent default header value.
|
||||
var UserAgent = fmt.Sprintf("Open Policy Agent/%s (%s, %s)", version.Version, runtime.GOOS, runtime.GOARCH)
|
||||
Generated
Vendored
+77
@@ -0,0 +1,77 @@
|
||||
// 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 constant contains WASM constant definitions.
|
||||
package constant
|
||||
|
||||
// Magic bytes at the beginning of every WASM file ("\0asm").
|
||||
const Magic = uint32(0x6D736100)
|
||||
|
||||
// Version defines the WASM version.
|
||||
const Version = uint32(1)
|
||||
|
||||
// WASM module section IDs.
|
||||
const (
|
||||
CustomSectionID uint8 = iota
|
||||
TypeSectionID
|
||||
ImportSectionID
|
||||
FunctionSectionID
|
||||
TableSectionID
|
||||
MemorySectionID
|
||||
GlobalSectionID
|
||||
ExportSectionID
|
||||
StartSectionID
|
||||
ElementSectionID
|
||||
CodeSectionID
|
||||
DataSectionID
|
||||
)
|
||||
|
||||
// FunctionTypeID indicates the start of a function type definition.
|
||||
const FunctionTypeID = byte(0x60)
|
||||
|
||||
// ValueType represents an intrinsic value type in WASM.
|
||||
const (
|
||||
ValueTypeF64 byte = iota + 0x7C
|
||||
ValueTypeF32
|
||||
ValueTypeI64
|
||||
ValueTypeI32
|
||||
)
|
||||
|
||||
// WASM import descriptor types.
|
||||
const (
|
||||
ImportDescType byte = iota
|
||||
ImportDescTable
|
||||
ImportDescMem
|
||||
ImportDescGlobal
|
||||
)
|
||||
|
||||
// WASM export descriptor types.
|
||||
const (
|
||||
ExportDescType byte = iota
|
||||
ExportDescTable
|
||||
ExportDescMem
|
||||
ExportDescGlobal
|
||||
)
|
||||
|
||||
// ElementTypeAnyFunc indicates the type of a table import.
|
||||
const ElementTypeAnyFunc byte = 0x70
|
||||
|
||||
// BlockTypeEmpty represents a block type.
|
||||
const BlockTypeEmpty byte = 0x40
|
||||
|
||||
// WASM global varialbe mutability flag.
|
||||
const (
|
||||
Const byte = iota
|
||||
Mutable
|
||||
)
|
||||
|
||||
// NameSectionCustomID is the ID of the "Name" section Custom Section
|
||||
const NameSectionCustomID = "name"
|
||||
|
||||
// Subtypes of the 'name' custom section
|
||||
const (
|
||||
NameSectionModuleType byte = iota
|
||||
NameSectionFunctionsType
|
||||
NameSectionLocalsType
|
||||
)
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// 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 encoding implements WASM module reading and writing.
|
||||
package encoding
|
||||
+965
@@ -0,0 +1,965 @@
|
||||
// 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 encoding
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/open-policy-agent/opa/internal/leb128"
|
||||
"github.com/open-policy-agent/opa/internal/wasm/constant"
|
||||
"github.com/open-policy-agent/opa/internal/wasm/instruction"
|
||||
"github.com/open-policy-agent/opa/internal/wasm/module"
|
||||
"github.com/open-policy-agent/opa/internal/wasm/opcode"
|
||||
"github.com/open-policy-agent/opa/internal/wasm/types"
|
||||
)
|
||||
|
||||
// ReadModule reads a binary-encoded WASM module from r.
|
||||
func ReadModule(r io.Reader) (*module.Module, error) {
|
||||
|
||||
wr := &reader{r: r, n: 0}
|
||||
module, err := readModule(wr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("offset 0x%x: %w", wr.n, err)
|
||||
}
|
||||
|
||||
return module, nil
|
||||
}
|
||||
|
||||
// ReadCodeEntry reads a binary-encoded WASM code entry from r.
|
||||
func ReadCodeEntry(r io.Reader) (*module.CodeEntry, error) {
|
||||
|
||||
wr := &reader{r: r, n: 0}
|
||||
entry, err := readCodeEntry(wr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("offset 0x%x: %w", wr.n, err)
|
||||
}
|
||||
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
// CodeEntries returns the WASM code entries contained in r.
|
||||
func CodeEntries(m *module.Module) ([]*module.CodeEntry, error) {
|
||||
|
||||
entries := make([]*module.CodeEntry, len(m.Code.Segments))
|
||||
|
||||
for i, s := range m.Code.Segments {
|
||||
buf := bytes.NewBuffer(s.Code)
|
||||
entry, err := ReadCodeEntry(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entries[i] = entry
|
||||
}
|
||||
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
type reader struct {
|
||||
r io.Reader
|
||||
n int
|
||||
}
|
||||
|
||||
func (r *reader) Read(bs []byte) (int, error) {
|
||||
n, err := r.r.Read(bs)
|
||||
r.n += n
|
||||
return n, err
|
||||
}
|
||||
|
||||
func readModule(r io.Reader) (*module.Module, error) {
|
||||
|
||||
if err := readMagic(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := readVersion(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var m module.Module
|
||||
|
||||
if err := readSections(r, &m); err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
func readCodeEntry(r io.Reader) (*module.CodeEntry, error) {
|
||||
|
||||
var entry module.CodeEntry
|
||||
|
||||
if err := readLocals(r, &entry.Func.Locals); err != nil {
|
||||
return nil, fmt.Errorf("local declarations: %w", err)
|
||||
}
|
||||
|
||||
return &entry, readExpr(r, &entry.Func.Expr)
|
||||
}
|
||||
|
||||
func readMagic(r io.Reader) error {
|
||||
var v uint32
|
||||
if err := binary.Read(r, binary.LittleEndian, &v); err != nil {
|
||||
return err
|
||||
} else if v != constant.Magic {
|
||||
return errors.New("illegal magic value")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readVersion(r io.Reader) error {
|
||||
var v uint32
|
||||
if err := binary.Read(r, binary.LittleEndian, &v); err != nil {
|
||||
return err
|
||||
} else if v != constant.Version {
|
||||
return errors.New("illegal wasm version")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readSections(r io.Reader, m *module.Module) error {
|
||||
for {
|
||||
id, err := readByte(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
size, err := leb128.ReadVarUint32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf := make([]byte, size)
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bufr := bytes.NewReader(buf)
|
||||
|
||||
switch id {
|
||||
case constant.StartSectionID:
|
||||
if err := readStartSection(bufr, &m.Start); err != nil {
|
||||
return fmt.Errorf("start section: %w", err)
|
||||
}
|
||||
case constant.CustomSectionID:
|
||||
var name string
|
||||
if err := readByteVectorString(bufr, &name); err != nil {
|
||||
return fmt.Errorf("read custom section type: %w", err)
|
||||
}
|
||||
if name == "name" {
|
||||
if err := readCustomNameSections(bufr, &m.Names); err != nil {
|
||||
return fmt.Errorf("custom 'name' section: %w", err)
|
||||
}
|
||||
} else {
|
||||
if err := readCustomSection(bufr, name, &m.Customs); err != nil {
|
||||
return fmt.Errorf("custom section: %w", err)
|
||||
}
|
||||
}
|
||||
case constant.TypeSectionID:
|
||||
if err := readTypeSection(bufr, &m.Type); err != nil {
|
||||
return fmt.Errorf("type section: %w", err)
|
||||
}
|
||||
case constant.ImportSectionID:
|
||||
if err := readImportSection(bufr, &m.Import); err != nil {
|
||||
return fmt.Errorf("import section: %w", err)
|
||||
}
|
||||
case constant.TableSectionID:
|
||||
if err := readTableSection(bufr, &m.Table); err != nil {
|
||||
return fmt.Errorf("table section: %w", err)
|
||||
}
|
||||
case constant.MemorySectionID:
|
||||
if err := readMemorySection(bufr, &m.Memory); err != nil {
|
||||
return fmt.Errorf("memory section: %w", err)
|
||||
}
|
||||
case constant.GlobalSectionID:
|
||||
if err := readGlobalSection(bufr, &m.Global); err != nil {
|
||||
return fmt.Errorf("global section: %w", err)
|
||||
}
|
||||
case constant.FunctionSectionID:
|
||||
if err := readFunctionSection(bufr, &m.Function); err != nil {
|
||||
return fmt.Errorf("function section: %w", err)
|
||||
}
|
||||
case constant.ExportSectionID:
|
||||
if err := readExportSection(bufr, &m.Export); err != nil {
|
||||
return fmt.Errorf("export section: %w", err)
|
||||
}
|
||||
case constant.ElementSectionID:
|
||||
if err := readElementSection(bufr, &m.Element); err != nil {
|
||||
return fmt.Errorf("element section: %w", err)
|
||||
}
|
||||
case constant.DataSectionID:
|
||||
if err := readDataSection(bufr, &m.Data); err != nil {
|
||||
return fmt.Errorf("data section: %w", err)
|
||||
}
|
||||
case constant.CodeSectionID:
|
||||
if err := readRawCodeSection(bufr, &m.Code); err != nil {
|
||||
return fmt.Errorf("code section: %w", err)
|
||||
}
|
||||
default:
|
||||
return errors.New("illegal section id")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readCustomSection(r io.Reader, name string, s *[]module.CustomSection) error {
|
||||
buf, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*s = append(*s, module.CustomSection{
|
||||
Name: name,
|
||||
Data: buf,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func readCustomNameSections(r io.Reader, s *module.NameSection) error {
|
||||
for {
|
||||
id, err := readByte(r)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
n, err := leb128.ReadVarUint32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf := make([]byte, n)
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
return err
|
||||
}
|
||||
bufr := bytes.NewReader(buf)
|
||||
switch id {
|
||||
case constant.NameSectionModuleType:
|
||||
err = readNameSectionModule(bufr, s)
|
||||
case constant.NameSectionFunctionsType:
|
||||
err = readNameSectionFunctions(bufr, s)
|
||||
case constant.NameSectionLocalsType:
|
||||
err = readNameSectionLocals(bufr, s)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readNameSectionModule(r io.Reader, s *module.NameSection) error {
|
||||
return readByteVectorString(r, &s.Module)
|
||||
}
|
||||
|
||||
func readNameSectionFunctions(r io.Reader, s *module.NameSection) error {
|
||||
nm, err := readNameMap(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Functions = nm
|
||||
return nil
|
||||
}
|
||||
|
||||
func readNameMap(r io.Reader) ([]module.NameMap, error) {
|
||||
n, err := leb128.ReadVarUint32(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nm := make([]module.NameMap, n)
|
||||
for i := range n {
|
||||
var name string
|
||||
id, err := leb128.ReadVarUint32(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := readByteVectorString(r, &name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nm[i] = module.NameMap{Index: id, Name: name}
|
||||
}
|
||||
return nm, nil
|
||||
}
|
||||
|
||||
func readNameSectionLocals(r io.Reader, s *module.NameSection) error {
|
||||
n, err := leb128.ReadVarUint32(r) // length of vec(indirectnameassoc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for range n {
|
||||
id, err := leb128.ReadVarUint32(r) // func index
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nm, err := readNameMap(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, m := range nm {
|
||||
s.Locals = append(s.Locals, module.LocalNameMap{
|
||||
FuncIndex: id,
|
||||
NameMap: module.NameMap{
|
||||
Index: m.Index,
|
||||
Name: m.Name,
|
||||
}})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readStartSection(r io.Reader, s *module.StartSection) error {
|
||||
idx, err := leb128.ReadVarUint32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.FuncIndex = &idx
|
||||
return nil
|
||||
}
|
||||
|
||||
func readTypeSection(r io.Reader, s *module.TypeSection) error {
|
||||
|
||||
n, err := leb128.ReadVarUint32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for range n {
|
||||
|
||||
var ftype module.FunctionType
|
||||
if err := readFunctionType(r, &ftype); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Functions = append(s.Functions, ftype)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readImportSection(r io.Reader, s *module.ImportSection) error {
|
||||
|
||||
n, err := leb128.ReadVarUint32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for range n {
|
||||
|
||||
var imp module.Import
|
||||
|
||||
if err := readImport(r, &imp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Imports = append(s.Imports, imp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readTableSection(r io.Reader, s *module.TableSection) error {
|
||||
|
||||
n, err := leb128.ReadVarUint32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for range n {
|
||||
|
||||
var table module.Table
|
||||
|
||||
if elem, err := readByte(r); err != nil {
|
||||
return err
|
||||
} else if elem != constant.ElementTypeAnyFunc {
|
||||
return errors.New("illegal element type")
|
||||
}
|
||||
|
||||
table.Type = types.Anyfunc
|
||||
|
||||
if err := readLimits(r, &table.Lim); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Tables = append(s.Tables, table)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readMemorySection(r io.Reader, s *module.MemorySection) error {
|
||||
|
||||
n, err := leb128.ReadVarUint32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for range n {
|
||||
|
||||
var mem module.Memory
|
||||
|
||||
if err := readLimits(r, &mem.Lim); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Memories = append(s.Memories, mem)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readGlobalSection(r io.Reader, s *module.GlobalSection) error {
|
||||
|
||||
n, err := leb128.ReadVarUint32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for range n {
|
||||
|
||||
var global module.Global
|
||||
|
||||
if err := readGlobal(r, &global); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Globals = append(s.Globals, global)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readFunctionSection(r io.Reader, s *module.FunctionSection) error {
|
||||
return readVarUint32Vector(r, &s.TypeIndices)
|
||||
}
|
||||
|
||||
func readExportSection(r io.Reader, s *module.ExportSection) error {
|
||||
|
||||
n, err := leb128.ReadVarUint32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for range n {
|
||||
|
||||
var exp module.Export
|
||||
|
||||
if err := readExport(r, &exp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Exports = append(s.Exports, exp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readElementSection(r io.Reader, s *module.ElementSection) error {
|
||||
|
||||
n, err := leb128.ReadVarUint32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for range n {
|
||||
|
||||
var seg module.ElementSegment
|
||||
|
||||
if err := readElementSegment(r, &seg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Segments = append(s.Segments, seg)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readDataSection(r io.Reader, s *module.DataSection) error {
|
||||
|
||||
n, err := leb128.ReadVarUint32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for range n {
|
||||
|
||||
var seg module.DataSegment
|
||||
|
||||
if err := readDataSegment(r, &seg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Segments = append(s.Segments, seg)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readRawCodeSection(r io.Reader, s *module.RawCodeSection) error {
|
||||
|
||||
n, err := leb128.ReadVarUint32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for range n {
|
||||
var seg module.RawCodeSegment
|
||||
|
||||
if err := readRawCodeSegment(r, &seg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Segments = append(s.Segments, seg)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readFunctionType(r io.Reader, ftype *module.FunctionType) error {
|
||||
|
||||
if b, err := readByte(r); err != nil {
|
||||
return err
|
||||
} else if b != constant.FunctionTypeID {
|
||||
return fmt.Errorf("illegal function type id 0x%x", b)
|
||||
}
|
||||
|
||||
if err := readValueTypeVector(r, &ftype.Params); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return readValueTypeVector(r, &ftype.Results)
|
||||
}
|
||||
|
||||
func readGlobal(r io.Reader, global *module.Global) error {
|
||||
|
||||
if err := readValueType(r, &global.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := readByte(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if b == 1 {
|
||||
global.Mutable = true
|
||||
} else if b != 0 {
|
||||
return errors.New("illegal mutability flag")
|
||||
}
|
||||
|
||||
return readConstantExpr(r, &global.Init)
|
||||
}
|
||||
|
||||
func readImport(r io.Reader, imp *module.Import) error {
|
||||
|
||||
if err := readByteVectorString(r, &imp.Module); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := readByteVectorString(r, &imp.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := readByte(r)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
if b == constant.ImportDescType {
|
||||
index, err := leb128.ReadVarUint32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
imp.Descriptor = module.FunctionImport{
|
||||
Func: index,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if b == constant.ImportDescTable {
|
||||
if elem, err := readByte(r); err != nil {
|
||||
return err
|
||||
} else if elem != constant.ElementTypeAnyFunc {
|
||||
return errors.New("illegal element type")
|
||||
}
|
||||
desc := module.TableImport{
|
||||
Type: types.Anyfunc,
|
||||
}
|
||||
if err := readLimits(r, &desc.Lim); err != nil {
|
||||
return err
|
||||
}
|
||||
imp.Descriptor = desc
|
||||
return nil
|
||||
}
|
||||
|
||||
if b == constant.ImportDescMem {
|
||||
desc := module.MemoryImport{}
|
||||
if err := readLimits(r, &desc.Mem.Lim); err != nil {
|
||||
return err
|
||||
}
|
||||
imp.Descriptor = desc
|
||||
return nil
|
||||
}
|
||||
|
||||
if b == constant.ImportDescGlobal {
|
||||
desc := module.GlobalImport{}
|
||||
if err := readValueType(r, &desc.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := readByte(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if b == 1 {
|
||||
desc.Mutable = true
|
||||
} else if b != 0 {
|
||||
return errors.New("illegal mutability flag")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("illegal import descriptor type")
|
||||
}
|
||||
|
||||
func readExport(r io.Reader, exp *module.Export) error {
|
||||
|
||||
if err := readByteVectorString(r, &exp.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := readByte(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch b {
|
||||
case constant.ExportDescType:
|
||||
exp.Descriptor.Type = module.FunctionExportType
|
||||
case constant.ExportDescTable:
|
||||
exp.Descriptor.Type = module.TableExportType
|
||||
case constant.ExportDescMem:
|
||||
exp.Descriptor.Type = module.MemoryExportType
|
||||
case constant.ExportDescGlobal:
|
||||
exp.Descriptor.Type = module.GlobalExportType
|
||||
default:
|
||||
return errors.New("illegal export descriptor type")
|
||||
}
|
||||
|
||||
exp.Descriptor.Index, err = leb128.ReadVarUint32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readElementSegment(r io.Reader, seg *module.ElementSegment) error {
|
||||
|
||||
if err := readVarUint32(r, &seg.Index); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := readConstantExpr(r, &seg.Offset); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return readVarUint32Vector(r, &seg.Indices)
|
||||
}
|
||||
|
||||
func readDataSegment(r io.Reader, seg *module.DataSegment) error {
|
||||
|
||||
if err := readVarUint32(r, &seg.Index); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := readConstantExpr(r, &seg.Offset); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return readByteVector(r, &seg.Init)
|
||||
}
|
||||
|
||||
func readRawCodeSegment(r io.Reader, seg *module.RawCodeSegment) error {
|
||||
return readByteVector(r, &seg.Code)
|
||||
}
|
||||
|
||||
func readConstantExpr(r io.Reader, expr *module.Expr) error {
|
||||
|
||||
instrs := make([]instruction.Instruction, 0)
|
||||
|
||||
for {
|
||||
b, err := readByte(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch opcode.Opcode(b) {
|
||||
case opcode.I32Const:
|
||||
i32, err := leb128.ReadVarInt32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
instrs = append(instrs, instruction.I32Const{Value: i32})
|
||||
case opcode.I64Const:
|
||||
i64, err := leb128.ReadVarInt64(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
instrs = append(instrs, instruction.I64Const{Value: i64})
|
||||
case opcode.End:
|
||||
expr.Instrs = instrs
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("illegal constant expr opcode 0x%x", b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readExpr(r io.Reader, expr *module.Expr) (err error) {
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
switch r := r.(type) {
|
||||
case error:
|
||||
err = r
|
||||
default:
|
||||
err = errors.New("unknown panic")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return readInstructions(r, &expr.Instrs)
|
||||
}
|
||||
|
||||
func readInstructions(r io.Reader, instrs *[]instruction.Instruction) error {
|
||||
|
||||
ret := make([]instruction.Instruction, 0)
|
||||
|
||||
for {
|
||||
b, err := readByte(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch opcode.Opcode(b) {
|
||||
case opcode.I32Const:
|
||||
ret = append(ret, instruction.I32Const{Value: leb128.MustReadVarInt32(r)})
|
||||
case opcode.I64Const:
|
||||
ret = append(ret, instruction.I64Const{Value: leb128.MustReadVarInt64(r)})
|
||||
case opcode.I32Eqz:
|
||||
ret = append(ret, instruction.I32Eqz{})
|
||||
case opcode.GetLocal:
|
||||
ret = append(ret, instruction.GetLocal{Index: leb128.MustReadVarUint32(r)})
|
||||
case opcode.SetLocal:
|
||||
ret = append(ret, instruction.SetLocal{Index: leb128.MustReadVarUint32(r)})
|
||||
case opcode.Call:
|
||||
ret = append(ret, instruction.Call{Index: leb128.MustReadVarUint32(r)})
|
||||
case opcode.CallIndirect:
|
||||
ret = append(ret, instruction.CallIndirect{
|
||||
Index: leb128.MustReadVarUint32(r),
|
||||
Reserved: mustReadByte(r),
|
||||
})
|
||||
case opcode.BrIf:
|
||||
ret = append(ret, instruction.BrIf{Index: leb128.MustReadVarUint32(r)})
|
||||
case opcode.Return:
|
||||
ret = append(ret, instruction.Return{})
|
||||
case opcode.Block:
|
||||
block := instruction.Block{}
|
||||
if err := readBlockValueType(r, block.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := readInstructions(r, &block.Instrs); err != nil {
|
||||
return err
|
||||
}
|
||||
ret = append(ret, block)
|
||||
case opcode.Loop:
|
||||
loop := instruction.Loop{}
|
||||
if err := readBlockValueType(r, loop.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := readInstructions(r, &loop.Instrs); err != nil {
|
||||
return err
|
||||
}
|
||||
ret = append(ret, loop)
|
||||
case opcode.End:
|
||||
*instrs = ret
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("illegal opcode 0x%x", b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mustReadByte(r io.Reader) byte {
|
||||
b, err := readByte(r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func readLimits(r io.Reader, l *module.Limit) error {
|
||||
|
||||
b, err := readByte(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
minLim, err := leb128.ReadVarUint32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.Min = minLim
|
||||
|
||||
if b == 1 {
|
||||
maxLim, err := leb128.ReadVarUint32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.Max = &maxLim
|
||||
} else if b != 0 {
|
||||
return errors.New("illegal limit flag")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readLocals(r io.Reader, locals *[]module.LocalDeclaration) error {
|
||||
|
||||
n, err := leb128.ReadVarUint32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ret := make([]module.LocalDeclaration, n)
|
||||
|
||||
for i := range n {
|
||||
if err := readVarUint32(r, &ret[i].Count); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := readValueType(r, &ret[i].Type); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
*locals = ret
|
||||
return nil
|
||||
}
|
||||
|
||||
func readByteVector(r io.Reader, v *[]byte) error {
|
||||
|
||||
n, err := leb128.ReadVarUint32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf := make([]byte, n)
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*v = buf
|
||||
return nil
|
||||
}
|
||||
|
||||
func readByteVectorString(r io.Reader, v *string) error {
|
||||
|
||||
var buf []byte
|
||||
|
||||
if err := readByteVector(r, &buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*v = string(buf)
|
||||
return nil
|
||||
}
|
||||
|
||||
func readVarUint32Vector(r io.Reader, v *[]uint32) error {
|
||||
|
||||
n, err := leb128.ReadVarUint32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ret := make([]uint32, n)
|
||||
|
||||
for i := range n {
|
||||
if err := readVarUint32(r, &ret[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
*v = ret
|
||||
return nil
|
||||
}
|
||||
|
||||
func readValueTypeVector(r io.Reader, v *[]types.ValueType) error {
|
||||
|
||||
n, err := leb128.ReadVarUint32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ret := make([]types.ValueType, n)
|
||||
|
||||
for i := range n {
|
||||
if err := readValueType(r, &ret[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
*v = ret
|
||||
return nil
|
||||
}
|
||||
|
||||
func readVarUint32(r io.Reader, v *uint32) error {
|
||||
var err error
|
||||
*v, err = leb128.ReadVarUint32(r)
|
||||
return err
|
||||
}
|
||||
|
||||
func readValueType(r io.Reader, v *types.ValueType) error {
|
||||
if b, err := readByte(r); err != nil {
|
||||
return err
|
||||
} else if b == constant.ValueTypeI32 {
|
||||
*v = types.I32
|
||||
} else if b == constant.ValueTypeI64 {
|
||||
*v = types.I64
|
||||
} else if b == constant.ValueTypeF32 {
|
||||
*v = types.F32
|
||||
} else if b == constant.ValueTypeF64 {
|
||||
*v = types.F64
|
||||
} else {
|
||||
return fmt.Errorf("illegal value type: 0x%x", b)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readBlockValueType(r io.Reader, v *types.ValueType) error {
|
||||
if b, err := readByte(r); err != nil {
|
||||
return err
|
||||
} else if b == constant.ValueTypeI32 {
|
||||
*v = types.I32
|
||||
} else if b == constant.ValueTypeI64 {
|
||||
*v = types.I64
|
||||
} else if b == constant.ValueTypeF32 {
|
||||
*v = types.F32
|
||||
} else if b == constant.ValueTypeF64 {
|
||||
*v = types.F64
|
||||
} else if b != constant.BlockTypeEmpty {
|
||||
return fmt.Errorf("illegal value type: 0x%x", b)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readByte(r io.Reader) (byte, error) {
|
||||
buf := make([]byte, 1)
|
||||
_, err := io.ReadFull(r, buf)
|
||||
return buf[0], err
|
||||
}
|
||||
+778
@@ -0,0 +1,778 @@
|
||||
// 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 encoding
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/open-policy-agent/opa/internal/leb128"
|
||||
"github.com/open-policy-agent/opa/internal/wasm/constant"
|
||||
"github.com/open-policy-agent/opa/internal/wasm/instruction"
|
||||
"github.com/open-policy-agent/opa/internal/wasm/module"
|
||||
"github.com/open-policy-agent/opa/internal/wasm/opcode"
|
||||
"github.com/open-policy-agent/opa/internal/wasm/types"
|
||||
)
|
||||
|
||||
// WriteModule writes a binary-encoded representation of module to w.
|
||||
func WriteModule(w io.Writer, module *module.Module) error {
|
||||
|
||||
if err := writeMagic(w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeVersion(w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if module == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := writeTypeSection(w, module.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeImportSection(w, module.Import); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeFunctionSection(w, module.Function); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeTableSection(w, module.Table); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeMemorySection(w, module.Memory); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeGlobalSection(w, module.Global); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeExportSection(w, module.Export); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeStartSection(w, module.Start); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeElementSection(w, module.Element); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeRawCodeSection(w, module.Code); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeDataSection(w, module.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeNameSection(w, module.Names); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, custom := range module.Customs {
|
||||
if err := writeCustomSection(w, custom); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteCodeEntry writes a binary encoded representation of entry to w.
|
||||
func WriteCodeEntry(w io.Writer, entry *module.CodeEntry) error {
|
||||
|
||||
if err := leb128.WriteVarUint32(w, uint32(len(entry.Func.Locals))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, local := range entry.Func.Locals {
|
||||
|
||||
if err := leb128.WriteVarUint32(w, local.Count); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeValueType(w, local.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return writeInstructions(w, entry.Func.Expr.Instrs)
|
||||
}
|
||||
|
||||
func writeMagic(w io.Writer) error {
|
||||
return binary.Write(w, binary.LittleEndian, constant.Magic)
|
||||
}
|
||||
|
||||
func writeVersion(w io.Writer) error {
|
||||
return binary.Write(w, binary.LittleEndian, constant.Version)
|
||||
}
|
||||
|
||||
func writeStartSection(w io.Writer, s module.StartSection) error {
|
||||
|
||||
if s.FuncIndex == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := writeByte(w, constant.StartSectionID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := leb128.WriteVarUint32(&buf, *s.FuncIndex); err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRawSection(w, &buf)
|
||||
}
|
||||
|
||||
func writeTypeSection(w io.Writer, s module.TypeSection) error {
|
||||
|
||||
if len(s.Functions) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := writeByte(w, constant.TypeSectionID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
if err := leb128.WriteVarUint32(&buf, uint32(len(s.Functions))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, fsig := range s.Functions {
|
||||
if err := writeFunctionType(&buf, fsig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return writeRawSection(w, &buf)
|
||||
}
|
||||
|
||||
func writeImportSection(w io.Writer, s module.ImportSection) error {
|
||||
|
||||
if len(s.Imports) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := writeByte(w, constant.ImportSectionID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
if err := leb128.WriteVarUint32(&buf, uint32(len(s.Imports))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, imp := range s.Imports {
|
||||
if err := writeImport(&buf, imp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return writeRawSection(w, &buf)
|
||||
}
|
||||
|
||||
func writeGlobalSection(w io.Writer, s module.GlobalSection) error {
|
||||
|
||||
if len(s.Globals) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := writeByte(w, constant.GlobalSectionID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
if err := leb128.WriteVarUint32(&buf, uint32(len(s.Globals))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, global := range s.Globals {
|
||||
if err := writeGlobal(&buf, global); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return writeRawSection(w, &buf)
|
||||
}
|
||||
|
||||
func writeFunctionSection(w io.Writer, s module.FunctionSection) error {
|
||||
|
||||
if len(s.TypeIndices) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := writeByte(w, constant.FunctionSectionID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
if err := leb128.WriteVarUint32(&buf, uint32(len(s.TypeIndices))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, idx := range s.TypeIndices {
|
||||
if err := leb128.WriteVarUint32(&buf, idx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return writeRawSection(w, &buf)
|
||||
}
|
||||
|
||||
func writeTableSection(w io.Writer, s module.TableSection) error {
|
||||
|
||||
if len(s.Tables) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := writeByte(w, constant.TableSectionID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
if err := leb128.WriteVarUint32(&buf, uint32(len(s.Tables))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, table := range s.Tables {
|
||||
switch table.Type {
|
||||
case types.Anyfunc:
|
||||
if err := writeByte(&buf, constant.ElementTypeAnyFunc); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.New("illegal table element type")
|
||||
}
|
||||
if err := writeLimits(&buf, table.Lim); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return writeRawSection(w, &buf)
|
||||
}
|
||||
|
||||
func writeMemorySection(w io.Writer, s module.MemorySection) error {
|
||||
|
||||
if len(s.Memories) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := writeByte(w, constant.MemorySectionID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
if err := leb128.WriteVarUint32(&buf, uint32(len(s.Memories))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, mem := range s.Memories {
|
||||
if err := writeLimits(&buf, mem.Lim); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return writeRawSection(w, &buf)
|
||||
}
|
||||
|
||||
func writeExportSection(w io.Writer, s module.ExportSection) error {
|
||||
|
||||
if len(s.Exports) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := writeByte(w, constant.ExportSectionID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
if err := leb128.WriteVarUint32(&buf, uint32(len(s.Exports))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, exp := range s.Exports {
|
||||
if err := writeByteVector(&buf, []byte(exp.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
var tpe byte
|
||||
switch exp.Descriptor.Type {
|
||||
case module.FunctionExportType:
|
||||
tpe = constant.ExportDescType
|
||||
case module.TableExportType:
|
||||
tpe = constant.ExportDescTable
|
||||
case module.MemoryExportType:
|
||||
tpe = constant.ExportDescMem
|
||||
case module.GlobalExportType:
|
||||
tpe = constant.ExportDescGlobal
|
||||
default:
|
||||
return fmt.Errorf("illegal export descriptor type 0x%x", exp.Descriptor.Type)
|
||||
}
|
||||
if err := writeByte(&buf, tpe); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := leb128.WriteVarUint32(&buf, exp.Descriptor.Index); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return writeRawSection(w, &buf)
|
||||
}
|
||||
|
||||
func writeElementSection(w io.Writer, s module.ElementSection) error {
|
||||
|
||||
if len(s.Segments) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := writeByte(w, constant.ElementSectionID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
if err := leb128.WriteVarUint32(&buf, uint32(len(s.Segments))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, seg := range s.Segments {
|
||||
if err := leb128.WriteVarUint32(&buf, seg.Index); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeInstructions(&buf, seg.Offset.Instrs); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeVarUint32Vector(&buf, seg.Indices); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return writeRawSection(w, &buf)
|
||||
}
|
||||
|
||||
func writeRawCodeSection(w io.Writer, s module.RawCodeSection) error {
|
||||
|
||||
if len(s.Segments) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := writeByte(w, constant.CodeSectionID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
if err := leb128.WriteVarUint32(&buf, uint32(len(s.Segments))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, seg := range s.Segments {
|
||||
if err := leb128.WriteVarUint32(&buf, uint32(len(seg.Code))); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := buf.Write(seg.Code); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return writeRawSection(w, &buf)
|
||||
}
|
||||
|
||||
func writeDataSection(w io.Writer, s module.DataSection) error {
|
||||
|
||||
if len(s.Segments) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := writeByte(w, constant.DataSectionID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
if err := leb128.WriteVarUint32(&buf, uint32(len(s.Segments))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, seg := range s.Segments {
|
||||
if err := leb128.WriteVarUint32(&buf, seg.Index); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeInstructions(&buf, seg.Offset.Instrs); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeByteVector(&buf, seg.Init); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return writeRawSection(w, &buf)
|
||||
}
|
||||
|
||||
func writeNameSection(w io.Writer, s module.NameSection) error {
|
||||
if s.Module == "" && len(s.Functions) == 0 && len(s.Locals) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := writeByte(w, constant.CustomSectionID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := writeByteVector(&buf, []byte(constant.NameSectionCustomID)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// "module" subsection
|
||||
if s.Module != "" {
|
||||
if err := writeByte(&buf, constant.NameSectionModuleType); err != nil {
|
||||
return err
|
||||
}
|
||||
var mbuf bytes.Buffer
|
||||
if err := writeByteVector(&mbuf, []byte(s.Module)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeRawSection(&buf, &mbuf); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// "functions" subsection
|
||||
if len(s.Functions) != 0 {
|
||||
if err := writeByte(&buf, constant.NameSectionFunctionsType); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var fbuf bytes.Buffer
|
||||
if err := writeNameMap(&fbuf, s.Functions); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeRawSection(&buf, &fbuf); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// "locals" subsection
|
||||
if len(s.Locals) != 0 {
|
||||
if err := writeByte(&buf, constant.NameSectionLocalsType); err != nil {
|
||||
return err
|
||||
}
|
||||
funs := map[uint32][]module.NameMap{}
|
||||
for _, e := range s.Locals {
|
||||
funs[e.FuncIndex] = append(funs[e.FuncIndex], module.NameMap{Index: e.Index, Name: e.Name})
|
||||
}
|
||||
var lbuf bytes.Buffer
|
||||
if err := leb128.WriteVarUint32(&lbuf, uint32(len(funs))); err != nil {
|
||||
return err
|
||||
}
|
||||
for fidx, e := range funs {
|
||||
if err := leb128.WriteVarUint32(&lbuf, fidx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeNameMap(&lbuf, e); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := writeRawSection(&buf, &lbuf); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return writeRawSection(w, &buf)
|
||||
}
|
||||
|
||||
func writeNameMap(buf io.Writer, nm []module.NameMap) error {
|
||||
if err := leb128.WriteVarUint32(buf, uint32(len(nm))); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, m := range nm {
|
||||
if err := leb128.WriteVarUint32(buf, m.Index); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeByteVector(buf, []byte(m.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeCustomSection(w io.Writer, s module.CustomSection) error {
|
||||
|
||||
if err := writeByte(w, constant.CustomSectionID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := writeByteVector(&buf, []byte(s.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(&buf, bytes.NewReader(s.Data)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return writeRawSection(w, &buf)
|
||||
}
|
||||
|
||||
func writeFunctionType(w io.Writer, fsig module.FunctionType) error {
|
||||
|
||||
if err := writeByte(w, constant.FunctionTypeID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeValueTypeVector(w, fsig.Params); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return writeValueTypeVector(w, fsig.Results)
|
||||
}
|
||||
|
||||
func writeImport(w io.Writer, imp module.Import) error {
|
||||
|
||||
if err := writeByteVector(w, []byte(imp.Module)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeByteVector(w, []byte(imp.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch desc := imp.Descriptor.(type) {
|
||||
case module.FunctionImport:
|
||||
if err := writeByte(w, constant.ImportDescType); err != nil {
|
||||
return err
|
||||
}
|
||||
return leb128.WriteVarUint32(w, desc.Func)
|
||||
case module.TableImport:
|
||||
if err := writeByte(w, constant.ImportDescTable); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeByte(w, constant.ElementTypeAnyFunc); err != nil {
|
||||
return err
|
||||
}
|
||||
return writeLimits(w, desc.Lim)
|
||||
case module.MemoryImport:
|
||||
if err := writeByte(w, constant.ImportDescMem); err != nil {
|
||||
return err
|
||||
}
|
||||
return writeLimits(w, desc.Mem.Lim)
|
||||
case module.GlobalImport:
|
||||
if err := writeByte(w, constant.ImportDescGlobal); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeValueType(w, desc.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
if desc.Mutable {
|
||||
return writeByte(w, constant.Mutable)
|
||||
}
|
||||
return writeByte(w, constant.Const)
|
||||
default:
|
||||
return errors.New("illegal import descriptor type")
|
||||
}
|
||||
}
|
||||
|
||||
func writeGlobal(w io.Writer, global module.Global) error {
|
||||
|
||||
if err := writeValueType(w, global.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
if global.Mutable {
|
||||
err = writeByte(w, constant.Mutable)
|
||||
} else {
|
||||
err = writeByte(w, constant.Const)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return writeInstructions(w, global.Init.Instrs)
|
||||
}
|
||||
|
||||
func writeInstructions(w io.Writer, instrs []instruction.Instruction) error {
|
||||
|
||||
for i, instr := range instrs {
|
||||
|
||||
_, err := w.Write([]byte{byte(instr.Op())})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, arg := range instr.ImmediateArgs() {
|
||||
var err error
|
||||
switch arg := arg.(type) {
|
||||
case int32:
|
||||
err = leb128.WriteVarInt32(w, arg)
|
||||
case int64:
|
||||
err = leb128.WriteVarInt64(w, arg)
|
||||
case uint32:
|
||||
err = leb128.WriteVarUint32(w, arg)
|
||||
case uint64:
|
||||
err = leb128.WriteVarUint64(w, arg)
|
||||
case float32:
|
||||
u32 := math.Float32bits(arg)
|
||||
err = binary.Write(w, binary.LittleEndian, u32)
|
||||
case float64:
|
||||
u64 := math.Float64bits(arg)
|
||||
err = binary.Write(w, binary.LittleEndian, u64)
|
||||
case byte:
|
||||
_, err = w.Write([]byte{arg})
|
||||
default:
|
||||
return fmt.Errorf("illegal immediate argument type on instruction %d", i)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if si, ok := instr.(instruction.StructuredInstruction); ok {
|
||||
if err := writeBlockValueType(w, si.BlockType()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeInstructions(w, si.Instructions()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_, err := w.Write([]byte{byte(opcode.End)})
|
||||
return err
|
||||
}
|
||||
|
||||
func writeLimits(w io.Writer, lim module.Limit) error {
|
||||
if lim.Max == nil {
|
||||
if err := writeByte(w, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := writeByte(w, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := leb128.WriteVarUint32(w, lim.Min); err != nil {
|
||||
return err
|
||||
}
|
||||
if lim.Max != nil {
|
||||
return leb128.WriteVarUint32(w, *lim.Max)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeVarUint32Vector(w io.Writer, v []uint32) error {
|
||||
|
||||
if err := leb128.WriteVarUint32(w, uint32(len(v))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range v {
|
||||
if err := leb128.WriteVarUint32(w, v[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeValueTypeVector(w io.Writer, v []types.ValueType) error {
|
||||
|
||||
if err := leb128.WriteVarUint32(w, uint32(len(v))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range v {
|
||||
if err := writeValueType(w, v[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeBlockValueType(w io.Writer, v *types.ValueType) error {
|
||||
var b byte
|
||||
if v != nil {
|
||||
switch *v {
|
||||
case types.I32:
|
||||
b = constant.ValueTypeI32
|
||||
case types.I64:
|
||||
b = constant.ValueTypeI64
|
||||
case types.F32:
|
||||
b = constant.ValueTypeF32
|
||||
case types.F64:
|
||||
b = constant.ValueTypeF64
|
||||
}
|
||||
} else {
|
||||
b = constant.BlockTypeEmpty
|
||||
}
|
||||
return writeByte(w, b)
|
||||
}
|
||||
|
||||
func writeValueType(w io.Writer, v types.ValueType) error {
|
||||
var b byte
|
||||
switch v {
|
||||
case types.I32:
|
||||
b = constant.ValueTypeI32
|
||||
case types.I64:
|
||||
b = constant.ValueTypeI64
|
||||
case types.F32:
|
||||
b = constant.ValueTypeF32
|
||||
case types.F64:
|
||||
b = constant.ValueTypeF64
|
||||
}
|
||||
return writeByte(w, b)
|
||||
}
|
||||
|
||||
func writeRawSection(w io.Writer, buf *bytes.Buffer) error {
|
||||
|
||||
size := buf.Len()
|
||||
|
||||
if err := leb128.WriteVarUint32(w, uint32(size)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := io.Copy(w, buf)
|
||||
return err
|
||||
}
|
||||
|
||||
func writeByteVector(w io.Writer, bs []byte) error {
|
||||
|
||||
if err := leb128.WriteVarUint32(w, uint32(len(bs))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := w.Write(bs)
|
||||
return err
|
||||
}
|
||||
|
||||
func writeByte(w io.Writer, b byte) error {
|
||||
buf := make([]byte, 1)
|
||||
buf[0] = b
|
||||
_, err := w.Write(buf)
|
||||
return err
|
||||
}
|
||||
Generated
Vendored
+183
@@ -0,0 +1,183 @@
|
||||
// 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 instruction
|
||||
|
||||
import (
|
||||
"github.com/open-policy-agent/opa/internal/wasm/opcode"
|
||||
"github.com/open-policy-agent/opa/internal/wasm/types"
|
||||
)
|
||||
|
||||
// !!! If you find yourself adding support for more control
|
||||
// instructions (br_table, if, ...), please adapt the
|
||||
// `withControlInstr` functions of
|
||||
// `compiler/wasm/optimizations.go`
|
||||
|
||||
// Unreachable represents a WASM unreachable instruction.
|
||||
type Unreachable struct {
|
||||
NoImmediateArgs
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (Unreachable) Op() opcode.Opcode {
|
||||
return opcode.Unreachable
|
||||
}
|
||||
|
||||
// Nop represents a WASM no-op instruction.
|
||||
type Nop struct {
|
||||
NoImmediateArgs
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (Nop) Op() opcode.Opcode {
|
||||
return opcode.Nop
|
||||
}
|
||||
|
||||
// Block represents a WASM block instruction.
|
||||
type Block struct {
|
||||
NoImmediateArgs
|
||||
Type *types.ValueType
|
||||
Instrs []Instruction
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction
|
||||
func (Block) Op() opcode.Opcode {
|
||||
return opcode.Block
|
||||
}
|
||||
|
||||
// BlockType returns the type of the block's return value.
|
||||
func (i Block) BlockType() *types.ValueType {
|
||||
return i.Type
|
||||
}
|
||||
|
||||
// Instructions returns the instructions contained in the block.
|
||||
func (i Block) Instructions() []Instruction {
|
||||
return i.Instrs
|
||||
}
|
||||
|
||||
// If represents a WASM if instruction.
|
||||
// NOTE(sr): we only use if with one branch so far!
|
||||
type If struct {
|
||||
NoImmediateArgs
|
||||
Type *types.ValueType
|
||||
Instrs []Instruction
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (If) Op() opcode.Opcode {
|
||||
return opcode.If
|
||||
}
|
||||
|
||||
// BlockType returns the type of the if's THEN branch.
|
||||
func (i If) BlockType() *types.ValueType {
|
||||
return i.Type
|
||||
}
|
||||
|
||||
// Instructions represents the instructions contained in the if's THEN branch.
|
||||
func (i If) Instructions() []Instruction {
|
||||
return i.Instrs
|
||||
}
|
||||
|
||||
// Loop represents a WASM loop instruction.
|
||||
type Loop struct {
|
||||
NoImmediateArgs
|
||||
Type *types.ValueType
|
||||
Instrs []Instruction
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (Loop) Op() opcode.Opcode {
|
||||
return opcode.Loop
|
||||
}
|
||||
|
||||
// BlockType returns the type of the loop's return value.
|
||||
func (i Loop) BlockType() *types.ValueType {
|
||||
return i.Type
|
||||
}
|
||||
|
||||
// Instructions represents the instructions contained in the loop.
|
||||
func (i Loop) Instructions() []Instruction {
|
||||
return i.Instrs
|
||||
}
|
||||
|
||||
// Br represents a WASM br instruction.
|
||||
type Br struct {
|
||||
Index uint32
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (Br) Op() opcode.Opcode {
|
||||
return opcode.Br
|
||||
}
|
||||
|
||||
// ImmediateArgs returns the block index to break to.
|
||||
func (i Br) ImmediateArgs() []any {
|
||||
return []any{i.Index}
|
||||
}
|
||||
|
||||
// BrIf represents a WASM br_if instruction.
|
||||
type BrIf struct {
|
||||
Index uint32
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (BrIf) Op() opcode.Opcode {
|
||||
return opcode.BrIf
|
||||
}
|
||||
|
||||
// ImmediateArgs returns the block index to break to.
|
||||
func (i BrIf) ImmediateArgs() []any {
|
||||
return []any{i.Index}
|
||||
}
|
||||
|
||||
// Call represents a WASM call instruction.
|
||||
type Call struct {
|
||||
Index uint32
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (Call) Op() opcode.Opcode {
|
||||
return opcode.Call
|
||||
}
|
||||
|
||||
// ImmediateArgs returns the function index.
|
||||
func (i Call) ImmediateArgs() []any {
|
||||
return []any{i.Index}
|
||||
}
|
||||
|
||||
// CallIndirect represents a WASM call_indirect instruction.
|
||||
type CallIndirect struct {
|
||||
Index uint32 // type index
|
||||
Reserved byte // zero for now
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (CallIndirect) Op() opcode.Opcode {
|
||||
return opcode.CallIndirect
|
||||
}
|
||||
|
||||
// ImmediateArgs returns the function index.
|
||||
func (i CallIndirect) ImmediateArgs() []any {
|
||||
return []any{i.Index, i.Reserved}
|
||||
}
|
||||
|
||||
// Return represents a WASM return instruction.
|
||||
type Return struct {
|
||||
NoImmediateArgs
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (Return) Op() opcode.Opcode {
|
||||
return opcode.Return
|
||||
}
|
||||
|
||||
// End represents the special WASM end instruction.
|
||||
type End struct {
|
||||
NoImmediateArgs
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (End) Op() opcode.Opcode {
|
||||
return opcode.End
|
||||
}
|
||||
Generated
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
// 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 instruction defines WASM instruction types.
|
||||
package instruction
|
||||
|
||||
import (
|
||||
"github.com/open-policy-agent/opa/internal/wasm/opcode"
|
||||
"github.com/open-policy-agent/opa/internal/wasm/types"
|
||||
)
|
||||
|
||||
// NoImmediateArgs indicates the instruction has no immediate arguments.
|
||||
type NoImmediateArgs struct {
|
||||
}
|
||||
|
||||
// ImmediateArgs returns the immedate arguments of an instruction.
|
||||
func (NoImmediateArgs) ImmediateArgs() []any {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Instruction represents a single WASM instruction.
|
||||
type Instruction interface {
|
||||
Op() opcode.Opcode
|
||||
ImmediateArgs() []any
|
||||
}
|
||||
|
||||
// StructuredInstruction represents a structured control instruction like br_if.
|
||||
type StructuredInstruction interface {
|
||||
Instruction
|
||||
BlockType() *types.ValueType
|
||||
Instructions() []Instruction
|
||||
}
|
||||
Generated
Vendored
+39
@@ -0,0 +1,39 @@
|
||||
// Copyright 2019 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 instruction
|
||||
|
||||
import "github.com/open-policy-agent/opa/internal/wasm/opcode"
|
||||
|
||||
// I32Load represents the WASM i32.load instruction.
|
||||
type I32Load struct {
|
||||
Offset int32
|
||||
Align int32 // expressed as a power of two
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (I32Load) Op() opcode.Opcode {
|
||||
return opcode.I32Load
|
||||
}
|
||||
|
||||
// ImmediateArgs returns the static offset and alignment operands.
|
||||
func (i I32Load) ImmediateArgs() []any {
|
||||
return []any{i.Align, i.Offset}
|
||||
}
|
||||
|
||||
// I32Store represents the WASM i32.store instruction.
|
||||
type I32Store struct {
|
||||
Offset int32
|
||||
Align int32 // expressed as a power of two
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (I32Store) Op() opcode.Opcode {
|
||||
return opcode.I32Store
|
||||
}
|
||||
|
||||
// ImmediateArgs returns the static offset and alignment operands.
|
||||
func (i I32Store) ImmediateArgs() []any {
|
||||
return []any{i.Align, i.Offset}
|
||||
}
|
||||
Generated
Vendored
+199
@@ -0,0 +1,199 @@
|
||||
// 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 instruction
|
||||
|
||||
import (
|
||||
"github.com/open-policy-agent/opa/internal/wasm/opcode"
|
||||
)
|
||||
|
||||
// I32Const represents the WASM i32.const instruction.
|
||||
type I32Const struct {
|
||||
Value int32
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (I32Const) Op() opcode.Opcode {
|
||||
return opcode.I32Const
|
||||
}
|
||||
|
||||
// ImmediateArgs returns the i32 value to push onto the stack.
|
||||
func (i I32Const) ImmediateArgs() []any {
|
||||
return []any{i.Value}
|
||||
}
|
||||
|
||||
// I64Const represents the WASM i64.const instruction.
|
||||
type I64Const struct {
|
||||
Value int64
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (I64Const) Op() opcode.Opcode {
|
||||
return opcode.I64Const
|
||||
}
|
||||
|
||||
// ImmediateArgs returns the i64 value to push onto the stack.
|
||||
func (i I64Const) ImmediateArgs() []any {
|
||||
return []any{i.Value}
|
||||
}
|
||||
|
||||
// F32Const represents the WASM f32.const instruction.
|
||||
type F32Const struct {
|
||||
Value int32
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (F32Const) Op() opcode.Opcode {
|
||||
return opcode.F32Const
|
||||
}
|
||||
|
||||
// ImmediateArgs returns the f32 value to push onto the stack.
|
||||
func (i F32Const) ImmediateArgs() []any {
|
||||
return []any{i.Value}
|
||||
}
|
||||
|
||||
// F64Const represents the WASM f64.const instruction.
|
||||
type F64Const struct {
|
||||
Value float64
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (F64Const) Op() opcode.Opcode {
|
||||
return opcode.F64Const
|
||||
}
|
||||
|
||||
// ImmediateArgs returns the f64 value to push onto the stack.
|
||||
func (i F64Const) ImmediateArgs() []any {
|
||||
return []any{i.Value}
|
||||
}
|
||||
|
||||
// I32Eqz represents the WASM i32.eqz instruction.
|
||||
type I32Eqz struct {
|
||||
NoImmediateArgs
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (I32Eqz) Op() opcode.Opcode {
|
||||
return opcode.I32Eqz
|
||||
}
|
||||
|
||||
// I32Eq represents the WASM i32.eq instruction.
|
||||
type I32Eq struct {
|
||||
NoImmediateArgs
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (I32Eq) Op() opcode.Opcode {
|
||||
return opcode.I32Eq
|
||||
}
|
||||
|
||||
// I32Ne represents the WASM i32.ne instruction.
|
||||
type I32Ne struct {
|
||||
NoImmediateArgs
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (I32Ne) Op() opcode.Opcode {
|
||||
return opcode.I32Ne
|
||||
}
|
||||
|
||||
// I32GtS represents the WASM i32.gt_s instruction.
|
||||
type I32GtS struct {
|
||||
NoImmediateArgs
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (I32GtS) Op() opcode.Opcode {
|
||||
return opcode.I32GtS
|
||||
}
|
||||
|
||||
// I32GeS represents the WASM i32.ge_s instruction.
|
||||
type I32GeS struct {
|
||||
NoImmediateArgs
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (I32GeS) Op() opcode.Opcode {
|
||||
return opcode.I32GeS
|
||||
}
|
||||
|
||||
// I32LtS represents the WASM i32.lt_s instruction.
|
||||
type I32LtS struct {
|
||||
NoImmediateArgs
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (I32LtS) Op() opcode.Opcode {
|
||||
return opcode.I32LtS
|
||||
}
|
||||
|
||||
// I32LeS represents the WASM i32.le_s instruction.
|
||||
type I32LeS struct {
|
||||
NoImmediateArgs
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (I32LeS) Op() opcode.Opcode {
|
||||
return opcode.I32LeS
|
||||
}
|
||||
|
||||
// I32Add represents the WASM i32.add instruction.
|
||||
type I32Add struct {
|
||||
NoImmediateArgs
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (I32Add) Op() opcode.Opcode {
|
||||
return opcode.I32Add
|
||||
}
|
||||
|
||||
// I64Add represents the WASM i64.add instruction.
|
||||
type I64Add struct {
|
||||
NoImmediateArgs
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (I64Add) Op() opcode.Opcode {
|
||||
return opcode.I64Add
|
||||
}
|
||||
|
||||
// F32Add represents the WASM f32.add instruction.
|
||||
type F32Add struct {
|
||||
NoImmediateArgs
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (F32Add) Op() opcode.Opcode {
|
||||
return opcode.F32Add
|
||||
}
|
||||
|
||||
// F64Add represents the WASM f64.add instruction.
|
||||
type F64Add struct {
|
||||
NoImmediateArgs
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (F64Add) Op() opcode.Opcode {
|
||||
return opcode.F64Add
|
||||
}
|
||||
|
||||
// I32Mul represents the WASM i32.mul instruction.
|
||||
type I32Mul struct {
|
||||
NoImmediateArgs
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (I32Mul) Op() opcode.Opcode {
|
||||
return opcode.I32Mul
|
||||
}
|
||||
|
||||
// I32Sub represents the WASM i32.sub instruction.
|
||||
type I32Sub struct {
|
||||
NoImmediateArgs
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (I32Sub) Op() opcode.Opcode {
|
||||
return opcode.I32Sub
|
||||
}
|
||||
Generated
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
// Copyright 2021 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 instruction
|
||||
|
||||
import (
|
||||
"github.com/open-policy-agent/opa/internal/wasm/opcode"
|
||||
)
|
||||
|
||||
// Drop reprsents a WASM drop instruction.
|
||||
type Drop struct {
|
||||
NoImmediateArgs
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (Drop) Op() opcode.Opcode {
|
||||
return opcode.Drop
|
||||
}
|
||||
|
||||
// Select reprsents a WASM select instruction.
|
||||
type Select struct {
|
||||
NoImmediateArgs
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (Select) Op() opcode.Opcode {
|
||||
return opcode.Select
|
||||
}
|
||||
Generated
Vendored
+54
@@ -0,0 +1,54 @@
|
||||
// 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 instruction
|
||||
|
||||
import "github.com/open-policy-agent/opa/internal/wasm/opcode"
|
||||
|
||||
// GetLocal represents the WASM get_local instruction.
|
||||
type GetLocal struct {
|
||||
Index uint32
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (GetLocal) Op() opcode.Opcode {
|
||||
return opcode.GetLocal
|
||||
}
|
||||
|
||||
// ImmediateArgs returns the index of the local variable to push onto the stack.
|
||||
func (i GetLocal) ImmediateArgs() []any {
|
||||
return []any{i.Index}
|
||||
}
|
||||
|
||||
// SetLocal represents the WASM set_local instruction.
|
||||
type SetLocal struct {
|
||||
Index uint32
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (SetLocal) Op() opcode.Opcode {
|
||||
return opcode.SetLocal
|
||||
}
|
||||
|
||||
// ImmediateArgs returns the index of the local variable to set with the top of
|
||||
// the stack.
|
||||
func (i SetLocal) ImmediateArgs() []any {
|
||||
return []any{i.Index}
|
||||
}
|
||||
|
||||
// TeeLocal represents the WASM tee_local instruction.
|
||||
type TeeLocal struct {
|
||||
Index uint32
|
||||
}
|
||||
|
||||
// Op returns the opcode of the instruction.
|
||||
func (TeeLocal) Op() opcode.Opcode {
|
||||
return opcode.TeeLocal
|
||||
}
|
||||
|
||||
// ImmediateArgs returns the index of the local variable to "tee" with the top of
|
||||
// the stack (like set, but retaining the top of the stack).
|
||||
func (i TeeLocal) ImmediateArgs() []any {
|
||||
return []any{i.Index}
|
||||
}
|
||||
+385
@@ -0,0 +1,385 @@
|
||||
// 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 module
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/open-policy-agent/opa/internal/wasm/instruction"
|
||||
"github.com/open-policy-agent/opa/internal/wasm/types"
|
||||
)
|
||||
|
||||
type (
|
||||
// Module represents a WASM module.
|
||||
Module struct {
|
||||
Version uint32
|
||||
Start StartSection
|
||||
Type TypeSection
|
||||
Import ImportSection
|
||||
Function FunctionSection
|
||||
Table TableSection
|
||||
Memory MemorySection
|
||||
Element ElementSection
|
||||
Global GlobalSection
|
||||
Export ExportSection
|
||||
Code RawCodeSection
|
||||
Data DataSection
|
||||
Customs []CustomSection
|
||||
Names NameSection
|
||||
}
|
||||
|
||||
// StartSection represents a WASM start section.
|
||||
StartSection struct {
|
||||
FuncIndex *uint32
|
||||
}
|
||||
|
||||
// TypeSection represents a WASM type section.
|
||||
TypeSection struct {
|
||||
Functions []FunctionType
|
||||
}
|
||||
|
||||
// ImportSection represents a WASM import section.
|
||||
ImportSection struct {
|
||||
Imports []Import
|
||||
}
|
||||
|
||||
// FunctionSection represents a WASM function section.
|
||||
FunctionSection struct {
|
||||
TypeIndices []uint32
|
||||
}
|
||||
|
||||
// TableSection represents a WASM table section.
|
||||
TableSection struct {
|
||||
Tables []Table
|
||||
}
|
||||
|
||||
// MemorySection represents a Wasm memory section.
|
||||
MemorySection struct {
|
||||
Memories []Memory
|
||||
}
|
||||
|
||||
// ElementSection represents a WASM element section.
|
||||
ElementSection struct {
|
||||
Segments []ElementSegment
|
||||
}
|
||||
|
||||
// GlobalSection represents a WASM global section.
|
||||
GlobalSection struct {
|
||||
Globals []Global
|
||||
}
|
||||
|
||||
// ExportSection represents a WASM export section.
|
||||
ExportSection struct {
|
||||
Exports []Export
|
||||
}
|
||||
|
||||
// RawCodeSection represents a WASM code section. The code section is left as a
|
||||
// raw byte sequence.
|
||||
RawCodeSection struct {
|
||||
Segments []RawCodeSegment
|
||||
}
|
||||
|
||||
// DataSection represents a WASM data section.
|
||||
DataSection struct {
|
||||
Segments []DataSegment
|
||||
}
|
||||
|
||||
// CustomSection represents a WASM custom section.
|
||||
CustomSection struct {
|
||||
Name string
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// NameSection represents the WASM custom section "name".
|
||||
NameSection struct {
|
||||
Module string
|
||||
Functions []NameMap
|
||||
Locals []LocalNameMap
|
||||
}
|
||||
|
||||
// NameMap maps function or local arg indices to their names.
|
||||
NameMap struct {
|
||||
Index uint32
|
||||
Name string
|
||||
}
|
||||
|
||||
// LocalNameMap maps function indices, and argument indices for the args
|
||||
// of the indexed function to their names.
|
||||
LocalNameMap struct {
|
||||
FuncIndex uint32
|
||||
NameMap
|
||||
}
|
||||
|
||||
// FunctionType represents a WASM function type definition.
|
||||
FunctionType struct {
|
||||
Params []types.ValueType
|
||||
Results []types.ValueType
|
||||
}
|
||||
|
||||
// Import represents a WASM import statement.
|
||||
Import struct {
|
||||
Module string
|
||||
Name string
|
||||
Descriptor ImportDescriptor
|
||||
}
|
||||
|
||||
// ImportDescriptor represents a WASM import descriptor.
|
||||
ImportDescriptor interface {
|
||||
fmt.Stringer
|
||||
Kind() ImportDescriptorType
|
||||
}
|
||||
|
||||
// ImportDescriptorType defines allowed kinds of import descriptors.
|
||||
ImportDescriptorType int
|
||||
|
||||
// FunctionImport represents a WASM function import statement.
|
||||
FunctionImport struct {
|
||||
Func uint32
|
||||
}
|
||||
|
||||
// MemoryImport represents a WASM memory import statement.
|
||||
MemoryImport struct {
|
||||
Mem MemType
|
||||
}
|
||||
|
||||
// MemType defines the attributes of a memory import.
|
||||
MemType struct {
|
||||
Lim Limit
|
||||
}
|
||||
|
||||
// TableImport represents a WASM table import statement.
|
||||
TableImport struct {
|
||||
Type types.ElementType
|
||||
Lim Limit
|
||||
}
|
||||
|
||||
// ElementSegment represents a WASM element segment.
|
||||
ElementSegment struct {
|
||||
Index uint32
|
||||
Offset Expr
|
||||
Indices []uint32
|
||||
}
|
||||
|
||||
// GlobalImport represents a WASM global variable import statement.
|
||||
GlobalImport struct {
|
||||
Type types.ValueType
|
||||
Mutable bool
|
||||
}
|
||||
|
||||
// Limit represents a WASM limit.
|
||||
Limit struct {
|
||||
Min uint32
|
||||
Max *uint32
|
||||
}
|
||||
|
||||
// Table represents a WASM table statement.
|
||||
Table struct {
|
||||
Type types.ElementType
|
||||
Lim Limit
|
||||
}
|
||||
|
||||
// Memory represents a Wasm memory statement.
|
||||
Memory struct {
|
||||
Lim Limit
|
||||
}
|
||||
|
||||
// Global represents a WASM global statement.
|
||||
Global struct {
|
||||
Type types.ValueType
|
||||
Mutable bool
|
||||
Init Expr
|
||||
}
|
||||
|
||||
// Export represents a WASM export statement.
|
||||
Export struct {
|
||||
Name string
|
||||
Descriptor ExportDescriptor
|
||||
}
|
||||
|
||||
// ExportDescriptor represents a WASM export descriptor.
|
||||
ExportDescriptor struct {
|
||||
Type ExportDescriptorType
|
||||
Index uint32
|
||||
}
|
||||
|
||||
// ExportDescriptorType defines the allowed kinds of export descriptors.
|
||||
ExportDescriptorType int
|
||||
|
||||
// RawCodeSegment represents a binary-encoded WASM code segment.
|
||||
RawCodeSegment struct {
|
||||
Code []byte
|
||||
}
|
||||
|
||||
// DataSegment represents a WASM data segment.
|
||||
DataSegment struct {
|
||||
Index uint32
|
||||
Offset Expr
|
||||
Init []byte
|
||||
}
|
||||
|
||||
// Expr represents a WASM expression.
|
||||
Expr struct {
|
||||
Instrs []instruction.Instruction
|
||||
}
|
||||
|
||||
// CodeEntry represents a code segment entry.
|
||||
CodeEntry struct {
|
||||
Func Function
|
||||
}
|
||||
|
||||
// Function represents a function in a code segment.
|
||||
Function struct {
|
||||
Locals []LocalDeclaration
|
||||
Expr Expr
|
||||
}
|
||||
|
||||
// LocalDeclaration represents a local variable declaration.
|
||||
LocalDeclaration struct {
|
||||
Count uint32
|
||||
Type types.ValueType
|
||||
}
|
||||
)
|
||||
|
||||
// Defines the allowed kinds of imports.
|
||||
const (
|
||||
FunctionImportType ImportDescriptorType = iota
|
||||
TableImportType
|
||||
MemoryImportType
|
||||
GlobalImportType
|
||||
)
|
||||
|
||||
func (x ImportDescriptorType) String() string {
|
||||
switch x {
|
||||
case FunctionImportType:
|
||||
return "func"
|
||||
case TableImportType:
|
||||
return "table"
|
||||
case MemoryImportType:
|
||||
return "memory"
|
||||
case GlobalImportType:
|
||||
return "global"
|
||||
}
|
||||
panic("illegal value")
|
||||
}
|
||||
|
||||
// Defines the allowed kinds of exports.
|
||||
const (
|
||||
FunctionExportType ExportDescriptorType = iota
|
||||
TableExportType
|
||||
MemoryExportType
|
||||
GlobalExportType
|
||||
)
|
||||
|
||||
func (x ExportDescriptorType) String() string {
|
||||
switch x {
|
||||
case FunctionExportType:
|
||||
return "func"
|
||||
case TableExportType:
|
||||
return "table"
|
||||
case MemoryExportType:
|
||||
return "memory"
|
||||
case GlobalExportType:
|
||||
return "global"
|
||||
}
|
||||
panic("illegal value")
|
||||
}
|
||||
|
||||
// Kind returns the function import type kind.
|
||||
func (FunctionImport) Kind() ImportDescriptorType {
|
||||
return FunctionImportType
|
||||
}
|
||||
|
||||
func (i FunctionImport) String() string {
|
||||
return fmt.Sprintf("%v[type=%v]", i.Kind(), i.Func)
|
||||
}
|
||||
|
||||
// Kind returns the memory import type kind.
|
||||
func (MemoryImport) Kind() ImportDescriptorType {
|
||||
return MemoryImportType
|
||||
}
|
||||
|
||||
func (i MemoryImport) String() string {
|
||||
return fmt.Sprintf("%v[%v]", i.Kind(), i.Mem.Lim)
|
||||
}
|
||||
|
||||
// Kind returns the table import type kind.
|
||||
func (TableImport) Kind() ImportDescriptorType {
|
||||
return TableImportType
|
||||
}
|
||||
|
||||
func (i TableImport) String() string {
|
||||
return fmt.Sprintf("%v[%v, %v]", i.Kind(), i.Type, i.Lim)
|
||||
}
|
||||
|
||||
// Kind returns the global import type kind.
|
||||
func (GlobalImport) Kind() ImportDescriptorType {
|
||||
return GlobalImportType
|
||||
}
|
||||
|
||||
func (i GlobalImport) String() string {
|
||||
return fmt.Sprintf("%v[%v, mut=%v]", i.Kind(), i.Type, i.Mutable)
|
||||
}
|
||||
|
||||
func (tpe FunctionType) String() string {
|
||||
params := make([]string, len(tpe.Params))
|
||||
results := make([]string, len(tpe.Results))
|
||||
for i := range tpe.Params {
|
||||
params[i] = tpe.Params[i].String()
|
||||
}
|
||||
for i := range tpe.Results {
|
||||
results[i] = tpe.Results[i].String()
|
||||
}
|
||||
return "(" + strings.Join(params, ", ") + ") -> (" + strings.Join(results, ", ") + ")"
|
||||
}
|
||||
|
||||
// Equal returns true if tpe equals other.
|
||||
func (tpe FunctionType) Equal(other FunctionType) bool {
|
||||
|
||||
if len(tpe.Params) != len(other.Params) || len(tpe.Results) != len(other.Results) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range tpe.Params {
|
||||
if tpe.Params[i] != other.Params[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for i := range tpe.Results {
|
||||
if tpe.Results[i] != other.Results[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (imp Import) String() string {
|
||||
return fmt.Sprintf("%v %v.%v", imp.Descriptor.String(), imp.Module, imp.Name)
|
||||
}
|
||||
|
||||
func (exp Export) String() string {
|
||||
return fmt.Sprintf("%v[%v] %v", exp.Descriptor.Type, exp.Descriptor.Index, exp.Name)
|
||||
}
|
||||
|
||||
func (seg RawCodeSegment) String() string {
|
||||
return fmt.Sprintf("<code %d bytes>", len(seg.Code))
|
||||
}
|
||||
|
||||
func (seg DataSegment) String() string {
|
||||
return fmt.Sprintf("<data index=%v [%v] len=%d bytes>", seg.Index, seg.Offset, len(seg.Init))
|
||||
}
|
||||
|
||||
func (e Expr) String() string {
|
||||
return fmt.Sprintf("%d instr(s)", len(e.Instrs))
|
||||
}
|
||||
|
||||
func (lim Limit) String() string {
|
||||
if lim.Max == nil {
|
||||
return fmt.Sprintf("min=%v", lim.Min)
|
||||
}
|
||||
return fmt.Sprintf("min=%v max=%v", lim.Min, lim.Max)
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
// 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 module
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// PrettyOption defines options for controlling pretty printing.
|
||||
type PrettyOption struct {
|
||||
Contents bool // show raw byte content of data+code sections.
|
||||
}
|
||||
|
||||
// Pretty writes a human-readable representation of m to w.
|
||||
func Pretty(w io.Writer, m *Module, opts ...PrettyOption) {
|
||||
fmt.Fprintln(w, "version:", m.Version)
|
||||
fmt.Fprintln(w, "types:")
|
||||
for _, fn := range m.Type.Functions {
|
||||
fmt.Fprintln(w, " -", fn)
|
||||
}
|
||||
fmt.Fprintln(w, "imports:")
|
||||
for i, imp := range m.Import.Imports {
|
||||
if imp.Descriptor.Kind() == FunctionImportType {
|
||||
fmt.Printf(" - [%d] %v\n", i, imp)
|
||||
} else {
|
||||
fmt.Fprintln(w, " -", imp)
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(w, "functions:")
|
||||
for _, fn := range m.Function.TypeIndices {
|
||||
if fn >= uint32(len(m.Type.Functions)) {
|
||||
fmt.Fprintln(w, " -", "???")
|
||||
} else {
|
||||
fmt.Fprintln(w, " -", m.Type.Functions[fn])
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(w, "exports:")
|
||||
for _, exp := range m.Export.Exports {
|
||||
fmt.Fprintln(w, " -", exp)
|
||||
}
|
||||
fmt.Fprintln(w, "code:")
|
||||
for _, seg := range m.Code.Segments {
|
||||
fmt.Fprintln(w, " -", seg)
|
||||
}
|
||||
fmt.Fprintln(w, "data:")
|
||||
for _, seg := range m.Data.Segments {
|
||||
fmt.Fprintln(w, " -", seg)
|
||||
}
|
||||
if len(opts) == 0 {
|
||||
return
|
||||
}
|
||||
fmt.Fprintln(w)
|
||||
for _, opt := range opts {
|
||||
if opt.Contents {
|
||||
newline := false
|
||||
if len(m.Data.Segments) > 0 {
|
||||
fmt.Fprintln(w, "data section:")
|
||||
for _, seg := range m.Data.Segments {
|
||||
if newline {
|
||||
fmt.Fprintln(w)
|
||||
}
|
||||
fmt.Fprintln(w, hex.Dump(seg.Init))
|
||||
newline = true
|
||||
}
|
||||
newline = false
|
||||
}
|
||||
if len(m.Code.Segments) > 0 {
|
||||
fmt.Fprintln(w, "code section:")
|
||||
for _, seg := range m.Code.Segments {
|
||||
if newline {
|
||||
fmt.Fprintln(w)
|
||||
}
|
||||
fmt.Fprintln(w, hex.Dump(seg.Code))
|
||||
newline = true
|
||||
}
|
||||
newline = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
// 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 opcode contains constants and utilities for working with WASM opcodes.
|
||||
package opcode
|
||||
|
||||
// Opcode represents a WASM instruction opcode.
|
||||
type Opcode byte
|
||||
|
||||
// Control instructions.
|
||||
const (
|
||||
Unreachable Opcode = iota
|
||||
Nop
|
||||
Block
|
||||
Loop
|
||||
If
|
||||
Else
|
||||
)
|
||||
|
||||
const (
|
||||
// End defines the special end WASM opcode.
|
||||
End Opcode = 0x0B
|
||||
)
|
||||
|
||||
// Extended control instructions.
|
||||
const (
|
||||
Br Opcode = iota + 0x0C
|
||||
BrIf
|
||||
BrTable
|
||||
Return
|
||||
Call
|
||||
CallIndirect
|
||||
)
|
||||
|
||||
// Parameter instructions.
|
||||
const (
|
||||
Drop Opcode = iota + 0x1A
|
||||
Select
|
||||
)
|
||||
|
||||
// Variable instructions.
|
||||
const (
|
||||
GetLocal Opcode = iota + 0x20
|
||||
SetLocal
|
||||
TeeLocal
|
||||
GetGlobal
|
||||
SetGlobal
|
||||
)
|
||||
|
||||
// Memory instructions.
|
||||
const (
|
||||
I32Load Opcode = iota + 0x28
|
||||
I64Load
|
||||
F32Load
|
||||
F64Load
|
||||
I32Load8S
|
||||
I32Load8U
|
||||
I32Load16S
|
||||
I32Load16U
|
||||
I64Load8S
|
||||
I64Load8U
|
||||
I64Load16S
|
||||
I64Load16U
|
||||
I64Load32S
|
||||
I64Load32U
|
||||
I32Store
|
||||
I64Store
|
||||
F32Store
|
||||
F64Store
|
||||
I32Store8
|
||||
I32Store16
|
||||
I64Store8
|
||||
I64Store16
|
||||
I64Store32
|
||||
MemorySize
|
||||
MemoryGrow
|
||||
)
|
||||
|
||||
// Numeric instructions.
|
||||
const (
|
||||
I32Const Opcode = iota + 0x41
|
||||
I64Const
|
||||
F32Const
|
||||
F64Const
|
||||
|
||||
I32Eqz
|
||||
I32Eq
|
||||
I32Ne
|
||||
I32LtS
|
||||
I32LtU
|
||||
I32GtS
|
||||
I32GtU
|
||||
I32LeS
|
||||
I32LeU
|
||||
I32GeS
|
||||
I32GeU
|
||||
|
||||
I64Eqz
|
||||
I64Eq
|
||||
I64Ne
|
||||
I64LtS
|
||||
I64LtU
|
||||
I64GtS
|
||||
I64GtU
|
||||
I64LeS
|
||||
I64LeU
|
||||
I64GeS
|
||||
I64GeU
|
||||
|
||||
F32Eq
|
||||
F32Ne
|
||||
F32Lt
|
||||
F32Gt
|
||||
F32Le
|
||||
F32Ge
|
||||
|
||||
F64Eq
|
||||
F64Ne
|
||||
F64Lt
|
||||
F64Gt
|
||||
F64Le
|
||||
F64Ge
|
||||
|
||||
I32Clz
|
||||
I32Ctz
|
||||
I32Popcnt
|
||||
I32Add
|
||||
I32Sub
|
||||
I32Mul
|
||||
I32DivS
|
||||
I32DivU
|
||||
I32RemS
|
||||
I32RemU
|
||||
I32And
|
||||
I32Or
|
||||
I32Xor
|
||||
I32Shl
|
||||
I32ShrS
|
||||
I32ShrU
|
||||
I32Rotl
|
||||
I32Rotr
|
||||
|
||||
I64Clz
|
||||
I64Ctz
|
||||
I64Popcnt
|
||||
I64Add
|
||||
I64Sub
|
||||
I64Mul
|
||||
I64DivS
|
||||
I64DivU
|
||||
I64RemS
|
||||
I64RemU
|
||||
I64And
|
||||
I64Or
|
||||
I64Xor
|
||||
I64Shl
|
||||
I64ShrS
|
||||
I64ShrU
|
||||
I64Rotl
|
||||
I64Rotr
|
||||
|
||||
F32Abs
|
||||
F32Neg
|
||||
F32Ceil
|
||||
F32Floor
|
||||
F32Trunc
|
||||
F32Nearest
|
||||
F32Sqrt
|
||||
F32Add
|
||||
F32Sub
|
||||
F32Mul
|
||||
F32Div
|
||||
F32Min
|
||||
F32Max
|
||||
F32Copysign
|
||||
|
||||
F64Abs
|
||||
F64Neg
|
||||
F64Ceil
|
||||
F64Floor
|
||||
F64Trunc
|
||||
F64Nearest
|
||||
F64Sqrt
|
||||
F64Add
|
||||
F64Sub
|
||||
F64Mul
|
||||
F64Div
|
||||
F64Min
|
||||
F64Max
|
||||
F64Copysign
|
||||
|
||||
I32WrapI64
|
||||
I32TruncSF32
|
||||
I32TruncUF32
|
||||
I32TruncSF64
|
||||
I32TruncUF64
|
||||
I64ExtendSI32
|
||||
I64ExtendUI32
|
||||
I64TruncSF32
|
||||
I64TruncUF32
|
||||
I64TruncSF64
|
||||
I64TruncUF64
|
||||
F32ConvertSI32
|
||||
F32ConvertUI32
|
||||
F32ConvertSI64
|
||||
F32ConvertUI64
|
||||
F32DemoteF64
|
||||
F64ConvertSI32
|
||||
F64ConvertUI32
|
||||
F64ConvertSI64
|
||||
F64ConvertUI64
|
||||
F64PromoteF32
|
||||
I32ReinterpretF32
|
||||
I64ReinterpretF64
|
||||
F32ReinterpretI32
|
||||
F64ReinterpretI64
|
||||
)
|
||||
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
// Copyright 2021 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.
|
||||
|
||||
// +build opa_wasm generate
|
||||
|
||||
package capabilities
|
||||
|
||||
// ABIVersions returns the ABI versions that this SDK supports
|
||||
func ABIVersions() [][2]int {
|
||||
return [][2]int{{1, 1}, {1, 2}}
|
||||
}
|
||||
Generated
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
// Copyright 2021 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.
|
||||
|
||||
//go:build !opa_wasm && !generate
|
||||
// +build !opa_wasm,!generate
|
||||
|
||||
package capabilities
|
||||
|
||||
// ABIVersions returns the supported Wasm ABI versions for this
|
||||
// build: none
|
||||
func ABIVersions() [][2]int {
|
||||
return nil
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// 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 types defines the WASM value type constants.
|
||||
package types
|
||||
|
||||
// ValueType represents an intrinsic value in WASM.
|
||||
type ValueType int
|
||||
|
||||
// Defines the intrinsic value types.
|
||||
const (
|
||||
I32 ValueType = iota
|
||||
I64
|
||||
F32
|
||||
F64
|
||||
)
|
||||
|
||||
func (tpe ValueType) String() string {
|
||||
if tpe == I32 {
|
||||
return "i32"
|
||||
} else if tpe == I64 {
|
||||
return "i64"
|
||||
} else if tpe == F32 {
|
||||
return "f32"
|
||||
}
|
||||
return "f64"
|
||||
}
|
||||
|
||||
// ElementType defines the type of table elements.
|
||||
type ElementType int
|
||||
|
||||
const (
|
||||
// Anyfunc is the union of all table types.
|
||||
Anyfunc ElementType = iota
|
||||
)
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// 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 util
|
||||
|
||||
// PageSize represents the WASM page size in bytes.
|
||||
const PageSize = 65535
|
||||
|
||||
// Pages converts a byte size to Pages, rounding up as necessary.
|
||||
func Pages(n uint32) uint32 {
|
||||
pages := n / PageSize
|
||||
if pages*PageSize == n {
|
||||
return pages
|
||||
}
|
||||
|
||||
return pages + 1
|
||||
}
|
||||
Reference in New Issue
Block a user