build(deps): bump github.com/open-policy-agent/opa from 1.4.2 to 1.5.0

Bumps [github.com/open-policy-agent/opa](https://github.com/open-policy-agent/opa) from 1.4.2 to 1.5.0.
- [Release notes](https://github.com/open-policy-agent/opa/releases)
- [Changelog](https://github.com/open-policy-agent/opa/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-policy-agent/opa/compare/v1.4.2...v1.5.0)

---
updated-dependencies:
- dependency-name: github.com/open-policy-agent/opa
  dependency-version: 1.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2025-06-02 15:18:43 +00:00
committed by GitHub
parent 524e13ae89
commit 51805e710d
255 changed files with 10085 additions and 3161 deletions
+1 -1
View File
@@ -34,6 +34,6 @@ import (
// Sets are considered equal if and only if the symmetric difference of a and b
// is empty.
// Other comparisons are consistent but not defined.
func Compare(a, b interface{}) int {
func Compare(a, b any) int {
return v1.Compare(a, b)
}
+1 -1
View File
@@ -41,6 +41,6 @@ type ErrorDetails = v1.ErrorDetails
type Error = v1.Error
// NewError returns a new Error object.
func NewError(code string, loc *Location, f string, a ...interface{}) *Error {
func NewError(code string, loc *Location, f string, a ...any) *Error {
return v1.NewError(code, loc, f, a...)
}
+2 -2
View File
@@ -211,7 +211,7 @@ func NewBody(exprs ...*Expr) Body {
}
// NewExpr returns a new Expr object.
func NewExpr(terms interface{}) *Expr {
func NewExpr(terms any) *Expr {
return v1.NewExpr(terms)
}
@@ -222,7 +222,7 @@ func NewBuiltinExpr(terms ...*Term) *Expr {
}
// Copy returns a deep copy of the AST node x. If x is not an AST node, x is returned unmodified.
func Copy(x interface{}) interface{} {
func Copy(x any) any {
return v1.Copy(x)
}
+1 -1
View File
@@ -13,6 +13,6 @@ import (
// Pretty writes a pretty representation of the AST rooted at x to w.
//
// This is function is intended for debug purposes when inspecting ASTs.
func Pretty(w io.Writer, x interface{}) {
func Pretty(w io.Writer, x any) {
v1.Pretty(w, x)
}
+1 -1
View File
@@ -9,6 +9,6 @@ import (
)
// TypeName returns a human readable name for the AST element type.
func TypeName(x interface{}) string {
func TypeName(x any) string {
return v1.TypeName(x)
}
+11 -11
View File
@@ -30,7 +30,7 @@ func NewLocation(text []byte, file string, row int, col int) *Location {
type Value = v1.Value
// InterfaceToValue converts a native Go value x to a Value.
func InterfaceToValue(x interface{}) (Value, error) {
func InterfaceToValue(x any) (Value, error) {
return v1.InterfaceToValue(x)
}
@@ -40,7 +40,7 @@ func ValueFromReader(r io.Reader) (Value, error) {
}
// As converts v into a Go native type referred to by x.
func As(v Value, x interface{}) error {
func As(v Value, x any) error {
return v1.As(v, x)
}
@@ -62,13 +62,13 @@ func IsUnknownValueErr(err error) bool {
// ValueToInterface returns the Go representation of an AST value. The AST
// value should not contain any values that require evaluation (e.g., vars,
// comprehensions, etc.)
func ValueToInterface(v Value, resolver Resolver) (interface{}, error) {
func ValueToInterface(v Value, resolver Resolver) (any, error) {
return v1.ValueToInterface(v, resolver)
}
// JSON returns the JSON representation of v. The value must not contain any
// refs or terms that require evaluation (e.g., vars, comprehensions, etc.)
func JSON(v Value) (interface{}, error) {
func JSON(v Value) (any, error) {
return v1.JSON(v)
}
@@ -77,7 +77,7 @@ type JSONOpt = v1.JSONOpt
// JSONWithOpt returns the JSON representation of v. The value must not contain any
// refs or terms that require evaluation (e.g., vars, comprehensions, etc.)
func JSONWithOpt(v Value, opt JSONOpt) (interface{}, error) {
func JSONWithOpt(v Value, opt JSONOpt) (any, error) {
return v1.JSONWithOpt(v, opt)
}
@@ -85,14 +85,14 @@ func JSONWithOpt(v Value, opt JSONOpt) (interface{}, error) {
// refs or terms that require evaluation (e.g., vars, comprehensions, etc.) If
// the conversion fails, this function will panic. This function is mostly for
// test purposes.
func MustJSON(v Value) interface{} {
func MustJSON(v Value) any {
return v1.MustJSON(v)
}
// MustInterfaceToValue converts a native Go value x to a Value. If the
// conversion fails, this function will panic. This function is mostly for test
// purposes.
func MustInterfaceToValue(x interface{}) Value {
func MustInterfaceToValue(x any) Value {
return v1.MustInterfaceToValue(x)
}
@@ -115,17 +115,17 @@ func IsComprehension(x Value) bool {
}
// ContainsRefs returns true if the Value v contains refs.
func ContainsRefs(v interface{}) bool {
func ContainsRefs(v any) bool {
return v1.ContainsRefs(v)
}
// ContainsComprehensions returns true if the Value v contains comprehensions.
func ContainsComprehensions(v interface{}) bool {
func ContainsComprehensions(v any) bool {
return v1.ContainsComprehensions(v)
}
// ContainsClosures returns true if the Value v contains closures.
func ContainsClosures(v interface{}) bool {
func ContainsClosures(v any) bool {
return v1.ContainsClosures(v)
}
@@ -256,7 +256,7 @@ func ObjectTerm(o ...[2]*Term) *Term {
return v1.ObjectTerm(o...)
}
func LazyObject(blob map[string]interface{}) Object {
func LazyObject(blob map[string]any) Object {
return v1.LazyObject(blob)
}
+5 -5
View File
@@ -16,22 +16,22 @@ type Transformer = v1.Transformer
// Transform iterates the AST and calls the Transform function on the
// Transformer t for x before recursing.
func Transform(t Transformer, x interface{}) (interface{}, error) {
func Transform(t Transformer, x any) (any, error) {
return v1.Transform(t, x)
}
// TransformRefs calls the function f on all references under x.
func TransformRefs(x interface{}, f func(Ref) (Value, error)) (interface{}, error) {
func TransformRefs(x any, f func(Ref) (Value, error)) (any, error) {
return v1.TransformRefs(x, f)
}
// TransformVars calls the function f on all vars under x.
func TransformVars(x interface{}, f func(Var) (Value, error)) (interface{}, error) {
func TransformVars(x any, f func(Var) (Value, error)) (any, error) {
return v1.TransformVars(x, f)
}
// TransformComprehensions calls the functio nf on all comprehensions under x.
func TransformComprehensions(x interface{}, f func(interface{}) (Value, error)) (interface{}, error) {
func TransformComprehensions(x any, f func(any) (Value, error)) (any, error) {
return v1.TransformComprehensions(x, f)
}
@@ -41,6 +41,6 @@ type GenericTransformer = v1.GenericTransformer
// NewGenericTransformer returns a new GenericTransformer that will transform
// AST nodes using the function f.
func NewGenericTransformer(f func(x interface{}) (interface{}, error)) *GenericTransformer {
func NewGenericTransformer(f func(x any) (any, error)) *GenericTransformer {
return v1.NewGenericTransformer(f)
}
+13 -13
View File
@@ -21,68 +21,68 @@ type BeforeAndAfterVisitor = v1.BeforeAndAfterVisitor
// Walk iterates the AST by calling the Visit function on the Visitor
// v for x before recursing.
// Deprecated: use GenericVisitor.Walk
func Walk(v Visitor, x interface{}) {
func Walk(v Visitor, x any) {
v1.Walk(v, x)
}
// WalkBeforeAndAfter iterates the AST by calling the Visit function on the
// Visitor v for x before recursing.
// Deprecated: use GenericVisitor.Walk
func WalkBeforeAndAfter(v BeforeAndAfterVisitor, x interface{}) {
func WalkBeforeAndAfter(v BeforeAndAfterVisitor, x any) {
v1.WalkBeforeAndAfter(v, x)
}
// WalkVars calls the function f on all vars under x. If the function f
// returns true, AST nodes under the last node will not be visited.
func WalkVars(x interface{}, f func(Var) bool) {
func WalkVars(x any, f func(Var) bool) {
v1.WalkVars(x, f)
}
// WalkClosures calls the function f on all closures under x. If the function f
// returns true, AST nodes under the last node will not be visited.
func WalkClosures(x interface{}, f func(interface{}) bool) {
func WalkClosures(x any, f func(any) bool) {
v1.WalkClosures(x, f)
}
// WalkRefs calls the function f on all references under x. If the function f
// returns true, AST nodes under the last node will not be visited.
func WalkRefs(x interface{}, f func(Ref) bool) {
func WalkRefs(x any, f func(Ref) bool) {
v1.WalkRefs(x, f)
}
// WalkTerms calls the function f on all terms under x. If the function f
// returns true, AST nodes under the last node will not be visited.
func WalkTerms(x interface{}, f func(*Term) bool) {
func WalkTerms(x any, f func(*Term) bool) {
v1.WalkTerms(x, f)
}
// WalkWiths calls the function f on all with modifiers under x. If the function f
// returns true, AST nodes under the last node will not be visited.
func WalkWiths(x interface{}, f func(*With) bool) {
func WalkWiths(x any, f func(*With) bool) {
v1.WalkWiths(x, f)
}
// WalkExprs calls the function f on all expressions under x. If the function f
// returns true, AST nodes under the last node will not be visited.
func WalkExprs(x interface{}, f func(*Expr) bool) {
func WalkExprs(x any, f func(*Expr) bool) {
v1.WalkExprs(x, f)
}
// WalkBodies calls the function f on all bodies under x. If the function f
// returns true, AST nodes under the last node will not be visited.
func WalkBodies(x interface{}, f func(Body) bool) {
func WalkBodies(x any, f func(Body) bool) {
v1.WalkBodies(x, f)
}
// WalkRules calls the function f on all rules under x. If the function f
// returns true, AST nodes under the last node will not be visited.
func WalkRules(x interface{}, f func(*Rule) bool) {
func WalkRules(x any, f func(*Rule) bool) {
v1.WalkRules(x, f)
}
// WalkNodes calls the function f on all nodes under x. If the function f
// returns true, AST nodes under the last node will not be visited.
func WalkNodes(x interface{}, f func(Node) bool) {
func WalkNodes(x any, f func(Node) bool) {
v1.WalkNodes(x, f)
}
@@ -93,7 +93,7 @@ type GenericVisitor = v1.GenericVisitor
// NewGenericVisitor returns a new GenericVisitor that will invoke the function
// f on AST nodes.
func NewGenericVisitor(f func(x interface{}) bool) *GenericVisitor {
func NewGenericVisitor(f func(x any) bool) *GenericVisitor {
return v1.NewGenericVisitor(f)
}
@@ -105,7 +105,7 @@ type BeforeAfterVisitor = v1.BeforeAfterVisitor
// NewBeforeAfterVisitor returns a new BeforeAndAfterVisitor that
// will invoke the functions before and after AST nodes.
func NewBeforeAfterVisitor(before func(x interface{}) bool, after func(x interface{})) *BeforeAfterVisitor {
func NewBeforeAfterVisitor(before func(x any) bool, after func(x any)) *BeforeAfterVisitor {
return v1.NewBeforeAfterVisitor(before, after)
}
+32 -3
View File
@@ -7,6 +7,7 @@ package bundle
import (
"context"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/storage"
v1 "github.com/open-policy-agent/opa/v1/bundle"
)
@@ -70,7 +71,7 @@ func ReadBundleRevisionFromStore(ctx context.Context, store storage.Store, txn s
// ReadBundleMetadataFromStore returns the metadata in the specified bundle.
// If the bundle is not activated, this function will return
// storage NotFound error.
func ReadBundleMetadataFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string) (map[string]interface{}, error) {
func ReadBundleMetadataFromStore(ctx context.Context, store storage.Store, txn storage.Transaction, name string) (map[string]any, error) {
return v1.ReadBundleMetadataFromStore(ctx, store, txn, name)
}
@@ -87,7 +88,7 @@ type ActivateOpts = v1.ActivateOpts
// Activate the bundle(s) by loading into the given Store. This will load policies, data, and record
// the manifest in storage. The compiler provided will have had the polices compiled on it.
func Activate(opts *ActivateOpts) error {
return v1.Activate(opts)
return v1.Activate(setActivateDefaultRegoVersion(opts))
}
// DeactivateOpts defines options for the Deactivate API call
@@ -95,7 +96,7 @@ type DeactivateOpts = v1.DeactivateOpts
// Deactivate the bundle(s). This will erase associated data, policies, and the manifest entry from the store.
func Deactivate(opts *DeactivateOpts) error {
return v1.Deactivate(opts)
return v1.Deactivate(setDeactivateDefaultRegoVersion(opts))
}
// LegacyWriteManifestToStore will write the bundle manifest to the older single (unnamed) bundle manifest location.
@@ -121,3 +122,31 @@ func LegacyReadRevisionFromStore(ctx context.Context, store storage.Store, txn s
func ActivateLegacy(opts *ActivateOpts) error {
return v1.ActivateLegacy(opts)
}
func setActivateDefaultRegoVersion(opts *ActivateOpts) *ActivateOpts {
if opts == nil {
return nil
}
if opts.ParserOptions.RegoVersion == ast.RegoUndefined {
cpy := *opts
cpy.ParserOptions.RegoVersion = ast.DefaultRegoVersion
return &cpy
}
return opts
}
func setDeactivateDefaultRegoVersion(opts *DeactivateOpts) *DeactivateOpts {
if opts == nil {
return nil
}
if opts.ParserOptions.RegoVersion == ast.RegoUndefined {
cpy := *opts
cpy.ParserOptions.RegoVersion = ast.DefaultRegoVersion
return &cpy
}
return opts
}
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -70,7 +70,7 @@ func ParseServicesConfig(opts ServiceOptions) (map[string]rest.Client, error) {
// read from disk (if specified) and overrides will be applied. If no config file is
// specified, the overrides can still be applied to an empty config.
func Load(configFile string, overrides []string, overrideFiles []string) ([]byte, error) {
baseConf := map[string]interface{}{}
baseConf := map[string]any{}
// User specified config file
if configFile != "" {
@@ -88,7 +88,7 @@ func Load(configFile string, overrides []string, overrideFiles []string) ([]byte
}
}
overrideConf := map[string]interface{}{}
overrideConf := map[string]any{}
// User specified a config override via --set
for _, override := range overrides {
@@ -100,7 +100,7 @@ func Load(configFile string, overrides []string, overrideFiles []string) ([]byte
// User specified a config override value via --set-file
for _, override := range overrideFiles {
reader := func(rs []rune) (interface{}, error) {
reader := func(rs []rune) (any, error) {
bytes, err := os.ReadFile(string(rs))
value := strings.TrimSpace(string(bytes))
return value, err
@@ -141,21 +141,21 @@ func subEnvVars(s string) string {
}
// mergeValues will merge source and destination map, preferring values from the source map
func mergeValues(dest map[string]interface{}, src map[string]interface{}) map[string]interface{} {
func mergeValues(dest map[string]any, src map[string]any) map[string]any {
for k, v := range src {
// If the key doesn't exist already, then just set the key to that value
if _, exists := dest[k]; !exists {
dest[k] = v
continue
}
nextMap, ok := v.(map[string]interface{})
nextMap, ok := v.(map[string]any)
// If it isn't another map, overwrite the value
if !ok {
dest[k] = v
continue
}
// Edge case: If the key exists in the destination, but isn't a map
destMap, isMap := dest[k].(map[string]interface{})
destMap, isMap := dest[k].(map[string]any)
// If the source map has a map for this key, prefer it
if !isMap {
dest[k] = v
+1 -1
View File
@@ -8,7 +8,7 @@ import (
// Debug allows printing debug messages.
type Debug interface {
// Printf prints, with a short file:line-number prefix
Printf(format string, args ...interface{})
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
+8 -8
View File
@@ -5,25 +5,25 @@
package deepcopy
// DeepCopy performs a recursive deep copy for nested slices/maps and
// returns the copied object. Supports []interface{}
// and map[string]interface{} only
func DeepCopy(val interface{}) interface{} {
// returns the copied object. Supports []any
// and map[string]any only
func DeepCopy(val any) any {
switch val := val.(type) {
case []interface{}:
cpy := make([]interface{}, len(val))
case []any:
cpy := make([]any, len(val))
for i := range cpy {
cpy[i] = DeepCopy(val[i])
}
return cpy
case map[string]interface{}:
case map[string]any:
return Map(val)
default:
return val
}
}
func Map(val map[string]interface{}) map[string]interface{} {
cpy := make(map[string]interface{}, len(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])
}
@@ -51,7 +51,7 @@ func shiftLower(bit byte, b []byte) byte {
// 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 := 0; i < len(b); i++ {
for i := range b {
newByte := b[i] << 1
newByte |= bit
bit = (b[i] & 0x80) >> 7
+5 -3
View File
@@ -723,15 +723,17 @@ func (e *EditTree) Unfold(path ast.Ref) (*EditTree, error) {
return child.Unfold(path[1:])
}
idxt := ast.InternedIntNumberTerm(idx)
// Fall back to looking up the key in e.value.
// Extend the tree if key is present. Error otherwise.
if v, err := x.Find(ast.Ref{ast.InternedIntNumberTerm(idx)}); err == nil {
if v, err := x.Find(ast.Ref{idxt}); err == nil {
// TODO: Consider a more efficient "Replace" function that special-cases this for arrays instead?
_, err := e.Delete(ast.InternedIntNumberTerm(idx))
_, err := e.Delete(idxt)
if err != nil {
return nil, err
}
child, err := e.Insert(ast.IntNumberTerm(idx), ast.NewTerm(v))
child, err := e.Insert(idxt, ast.NewTerm(v))
if err != nil {
return nil, err
}
+3 -5
View File
@@ -19,14 +19,12 @@ func FilterFutureImports(imps []*ast.Import) []*ast.Import {
return ret
}
var keywordsTerm = ast.StringTerm("keywords")
// 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(keywordsTerm)
path[1].Equal(ast.InternedStringTerm("keywords"))
}
// IsFutureKeyword returns true if the passed *ast.Import is `future.keywords.{kw}`
@@ -34,7 +32,7 @@ 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(keywordsTerm) &&
path[1].Equal(ast.InternedStringTerm("keywords")) &&
path[2].Equal(ast.StringTerm(kw))
}
@@ -42,7 +40,7 @@ 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(keywordsTerm) {
path[1].Equal(ast.InternedStringTerm("keywords")) {
if str, ok := path[2].Value.(ast.String); ok {
return string(str), true
}
+2 -2
View File
@@ -86,12 +86,12 @@ func (dc draftConfigs) GetSchemaURL(draft Draft) string {
return ""
}
func parseSchemaURL(documentNode interface{}) (string, *Draft, error) {
func parseSchemaURL(documentNode any) (string, *Draft, error) {
if _, ok := documentNode.(bool); ok {
return "", nil, nil
}
m, ok := documentNode.(map[string]interface{})
m, ok := documentNode.(map[string]any)
if !ok {
return "", nil, errors.New("schema is invalid")
}
+1 -1
View File
@@ -212,7 +212,7 @@ type (
)
// newError takes a ResultError type and sets the type, context, description, details, value, and field
func newError(err ResultError, context *JSONContext, value interface{}, locale locale, details ErrorDetails) {
func newError(err ResultError, context *JSONContext, value any, locale locale, details ErrorDetails) {
var t string
var d string
switch err.(type) {
@@ -14,7 +14,7 @@ type (
// FormatChecker is the interface all formatters added to FormatCheckerChain must implement
FormatChecker interface {
// IsFormat checks if input has the correct format
IsFormat(input interface{}) bool
IsFormat(input any) bool
}
// FormatCheckerChain holds the formatters
@@ -174,7 +174,7 @@ func (c *FormatCheckerChain) Has(name string) bool {
// 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 interface{}) bool {
func (c *FormatCheckerChain) IsFormat(name string, input any) bool {
lock.RLock()
f, ok := c.formatters[name]
lock.RUnlock()
@@ -188,7 +188,7 @@ func (c *FormatCheckerChain) IsFormat(name string, input interface{}) bool {
}
// IsFormat checks if input is a correctly formatted e-mail address
func (f EmailFormatChecker) IsFormat(input interface{}) bool {
func (f EmailFormatChecker) IsFormat(input any) bool {
asString, ok := input.(string)
if !ok {
return true
@@ -199,7 +199,7 @@ func (f EmailFormatChecker) IsFormat(input interface{}) bool {
}
// IsFormat checks if input is a correctly formatted IPv4-address
func (f IPV4FormatChecker) IsFormat(input interface{}) bool {
func (f IPV4FormatChecker) IsFormat(input any) bool {
asString, ok := input.(string)
if !ok {
return true
@@ -211,7 +211,7 @@ func (f IPV4FormatChecker) IsFormat(input interface{}) bool {
}
// IsFormat checks if input is a correctly formatted IPv6=address
func (f IPV6FormatChecker) IsFormat(input interface{}) bool {
func (f IPV6FormatChecker) IsFormat(input any) bool {
asString, ok := input.(string)
if !ok {
return true
@@ -223,7 +223,7 @@ func (f IPV6FormatChecker) IsFormat(input interface{}) bool {
}
// IsFormat checks if input is a correctly formatted date/time per RFC3339 5.6
func (f DateTimeFormatChecker) IsFormat(input interface{}) bool {
func (f DateTimeFormatChecker) IsFormat(input any) bool {
asString, ok := input.(string)
if !ok {
return true
@@ -247,7 +247,7 @@ func (f DateTimeFormatChecker) IsFormat(input interface{}) bool {
}
// IsFormat checks if input is a correctly formatted date (YYYY-MM-DD)
func (f DateFormatChecker) IsFormat(input interface{}) bool {
func (f DateFormatChecker) IsFormat(input any) bool {
asString, ok := input.(string)
if !ok {
return true
@@ -257,7 +257,7 @@ func (f DateFormatChecker) IsFormat(input interface{}) bool {
}
// IsFormat checks if input correctly formatted time (HH:MM:SS or HH:MM:SSZ-07:00)
func (f TimeFormatChecker) IsFormat(input interface{}) bool {
func (f TimeFormatChecker) IsFormat(input any) bool {
asString, ok := input.(string)
if !ok {
return true
@@ -272,7 +272,7 @@ func (f TimeFormatChecker) IsFormat(input interface{}) bool {
}
// IsFormat checks if input is correctly formatted URI with a valid Scheme per RFC3986
func (f URIFormatChecker) IsFormat(input interface{}) bool {
func (f URIFormatChecker) IsFormat(input any) bool {
asString, ok := input.(string)
if !ok {
return true
@@ -288,7 +288,7 @@ func (f URIFormatChecker) IsFormat(input interface{}) bool {
}
// IsFormat checks if input is a correctly formatted URI or relative-reference per RFC3986
func (f URIReferenceFormatChecker) IsFormat(input interface{}) bool {
func (f URIReferenceFormatChecker) IsFormat(input any) bool {
asString, ok := input.(string)
if !ok {
return true
@@ -299,7 +299,7 @@ func (f URIReferenceFormatChecker) IsFormat(input interface{}) bool {
}
// IsFormat checks if input is a correctly formatted URI template per RFC6570
func (f URITemplateFormatChecker) IsFormat(input interface{}) bool {
func (f URITemplateFormatChecker) IsFormat(input any) bool {
asString, ok := input.(string)
if !ok {
return true
@@ -314,7 +314,7 @@ func (f URITemplateFormatChecker) IsFormat(input interface{}) bool {
}
// IsFormat checks if input is a correctly formatted hostname
func (f HostnameFormatChecker) IsFormat(input interface{}) bool {
func (f HostnameFormatChecker) IsFormat(input any) bool {
asString, ok := input.(string)
if !ok {
return true
@@ -324,7 +324,7 @@ func (f HostnameFormatChecker) IsFormat(input interface{}) bool {
}
// IsFormat checks if input is a correctly formatted UUID
func (f UUIDFormatChecker) IsFormat(input interface{}) bool {
func (f UUIDFormatChecker) IsFormat(input any) bool {
asString, ok := input.(string)
if !ok {
return true
@@ -334,7 +334,7 @@ func (f UUIDFormatChecker) IsFormat(input interface{}) bool {
}
// IsFormat checks if input is a correctly formatted regular expression
func (f RegexFormatChecker) IsFormat(input interface{}) bool {
func (f RegexFormatChecker) IsFormat(input any) bool {
asString, ok := input.(string)
if !ok {
return true
@@ -348,7 +348,7 @@ func (f RegexFormatChecker) IsFormat(input interface{}) bool {
}
// IsFormat checks if input is a correctly formatted JSON Pointer per RFC6901
func (f JSONPointerFormatChecker) IsFormat(input interface{}) bool {
func (f JSONPointerFormatChecker) IsFormat(input any) bool {
asString, ok := input.(string)
if !ok {
return true
@@ -358,7 +358,7 @@ func (f JSONPointerFormatChecker) IsFormat(input interface{}) bool {
}
// IsFormat checks if input is a correctly formatted relative JSON Pointer
func (f RelativeJSONPointerFormatChecker) IsFormat(input interface{}) bool {
func (f RelativeJSONPointerFormatChecker) IsFormat(input any) bool {
asString, ok := input.(string)
if !ok {
return true
@@ -32,6 +32,6 @@ import (
const internalLogEnabled = false
func internalLog(format string, v ...interface{}) {
func internalLog(format string, v ...any) {
log.Printf(format, v...)
}
+24 -24
View File
@@ -77,8 +77,8 @@ var osFS = osFileSystem(os.Open)
// JSONLoader defines the JSON loader interface
type JSONLoader interface {
JSONSource() interface{}
LoadJSON() (interface{}, error)
JSONSource() any
LoadJSON() (any, error)
JSONReference() (gojsonreference.JsonReference, error)
LoaderFactory() JSONLoaderFactory
}
@@ -130,7 +130,7 @@ type jsonReferenceLoader struct {
source string
}
func (l *jsonReferenceLoader) JSONSource() interface{} {
func (l *jsonReferenceLoader) JSONSource() any {
return l.source
}
@@ -160,7 +160,7 @@ func NewReferenceLoaderFileSystem(source string, fs http.FileSystem) JSONLoader
}
}
func (l *jsonReferenceLoader) LoadJSON() (interface{}, error) {
func (l *jsonReferenceLoader) LoadJSON() (any, error) {
var err error
@@ -207,7 +207,7 @@ func (l *jsonReferenceLoader) LoadJSON() (interface{}, error) {
return nil, fmt.Errorf("remote reference loading disabled: %s", reference.String())
}
func (l *jsonReferenceLoader) loadFromHTTP(address string) (interface{}, error) {
func (l *jsonReferenceLoader) loadFromHTTP(address string) (any, error) {
resp, err := http.Get(address)
if err != nil {
@@ -227,7 +227,7 @@ func (l *jsonReferenceLoader) loadFromHTTP(address string) (interface{}, error)
return decodeJSONUsingNumber(bytes.NewReader(bodyBuff))
}
func (l *jsonReferenceLoader) loadFromFile(path string) (interface{}, error) {
func (l *jsonReferenceLoader) loadFromFile(path string) (any, error) {
f, err := l.fs.Open(path)
if err != nil {
return nil, err
@@ -249,7 +249,7 @@ type jsonStringLoader struct {
source string
}
func (l *jsonStringLoader) JSONSource() interface{} {
func (l *jsonStringLoader) JSONSource() any {
return l.source
}
@@ -266,7 +266,7 @@ func NewStringLoader(source string) JSONLoader {
return &jsonStringLoader{source: source}
}
func (l *jsonStringLoader) LoadJSON() (interface{}, error) {
func (l *jsonStringLoader) LoadJSON() (any, error) {
return decodeJSONUsingNumber(strings.NewReader(l.JSONSource().(string)))
@@ -278,7 +278,7 @@ type jsonBytesLoader struct {
source []byte
}
func (l *jsonBytesLoader) JSONSource() interface{} {
func (l *jsonBytesLoader) JSONSource() any {
return l.source
}
@@ -295,18 +295,18 @@ func NewBytesLoader(source []byte) JSONLoader {
return &jsonBytesLoader{source: source}
}
func (l *jsonBytesLoader) LoadJSON() (interface{}, error) {
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, interface{}, structs ...
// used to load JSONs from the code as maps, any, structs ...
type jsonGoLoader struct {
source interface{}
source any
}
func (l *jsonGoLoader) JSONSource() interface{} {
func (l *jsonGoLoader) JSONSource() any {
return l.source
}
@@ -319,11 +319,11 @@ func (l *jsonGoLoader) LoaderFactory() JSONLoaderFactory {
}
// NewGoLoader creates a new JSONLoader from a given Go struct
func NewGoLoader(source interface{}) JSONLoader {
func NewGoLoader(source any) JSONLoader {
return &jsonGoLoader{source: source}
}
func (l *jsonGoLoader) LoadJSON() (interface{}, error) {
func (l *jsonGoLoader) LoadJSON() (any, error) {
// convert it to a compliant JSON first to avoid types "mismatches"
@@ -352,11 +352,11 @@ func NewWriterLoader(source io.Writer) (JSONLoader, io.Writer) {
return &jsonIOLoader{buf: buf}, io.MultiWriter(source, buf)
}
func (l *jsonIOLoader) JSONSource() interface{} {
func (l *jsonIOLoader) JSONSource() any {
return l.buf.String()
}
func (l *jsonIOLoader) LoadJSON() (interface{}, error) {
func (l *jsonIOLoader) LoadJSON() (any, error) {
return decodeJSONUsingNumber(l.buf)
}
@@ -369,21 +369,21 @@ func (l *jsonIOLoader) LoaderFactory() JSONLoaderFactory {
}
// JSON raw loader
// In case the JSON is already marshalled to interface{} use this 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 interface{}
source any
}
// NewRawLoader creates a new JSON raw loader for the given source
func NewRawLoader(source interface{}) JSONLoader {
func NewRawLoader(source any) JSONLoader {
return &jsonRawLoader{source: source}
}
func (l *jsonRawLoader) JSONSource() interface{} {
func (l *jsonRawLoader) JSONSource() any {
return l.source
}
func (l *jsonRawLoader) LoadJSON() (interface{}, error) {
func (l *jsonRawLoader) LoadJSON() (any, error) {
return l.source, nil
}
func (l *jsonRawLoader) JSONReference() (gojsonreference.JsonReference, error) {
@@ -393,9 +393,9 @@ func (l *jsonRawLoader) LoaderFactory() JSONLoaderFactory {
return &DefaultJSONLoaderFactory{}
}
func decodeJSONUsingNumber(r io.Reader) (interface{}, error) {
func decodeJSONUsingNumber(r io.Reader) (any, error) {
var document interface{}
var document any
decoder := json.NewDecoder(r)
decoder.UseNumber()
+7 -7
View File
@@ -33,7 +33,7 @@ import (
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]interface{}
ErrorDetails map[string]any
// ResultError is the interface that library errors must implement
ResultError interface {
@@ -57,9 +57,9 @@ type (
// DescriptionFormat returns the format for the description in the default text/template format
DescriptionFormat() string
// SetValue sets the value related to the error
SetValue(interface{})
SetValue(any)
// Value returns the value related to the error
Value() interface{}
Value() any
// SetDetails sets the details specific to the error
SetDetails(ErrorDetails)
// Details returns details about the error
@@ -76,7 +76,7 @@ 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 interface{} // Value given by the JSON file that is the source of the error
value any // Value given by the JSON file that is the source of the error
details ErrorDetails
}
@@ -136,12 +136,12 @@ func (v *ResultErrorFields) DescriptionFormat() string {
}
// SetValue sets the value related to the error
func (v *ResultErrorFields) SetValue(value interface{}) {
func (v *ResultErrorFields) SetValue(value any) {
v.value = value
}
// Value returns the value related to the error
func (v *ResultErrorFields) Value() interface{} {
func (v *ResultErrorFields) Value() any {
return v.value
}
@@ -203,7 +203,7 @@ func (v *Result) AddError(err ResultError, details ErrorDetails) {
v.errors = append(v.errors, err)
}
func (v *Result) addInternalError(err ResultError, context *JSONContext, value interface{}, details ErrorDetails) {
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
+31 -31
View File
@@ -58,7 +58,7 @@ type Schema struct {
ReferencePool *schemaReferencePool
}
func (d *Schema) parse(document interface{}, draft Draft) error {
func (d *Schema) parse(document any, draft Draft) error {
d.RootSchema = &SubSchema{Property: StringRootSchemaProperty, Draft: &draft}
return d.parseSchema(document, d.RootSchema)
}
@@ -73,7 +73,7 @@ func (d *Schema) SetRootSchemaName(name string) {
// 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 interface{}, currentSchema *SubSchema) error {
func (d *Schema) parseSchema(documentNode any, currentSchema *SubSchema) error {
if currentSchema.Draft == nil {
if currentSchema.Parent == nil {
@@ -90,7 +90,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *SubSchema)
}
}
m, isMap := documentNode.(map[string]interface{})
m, isMap := documentNode.(map[string]any)
if !isMap {
return errors.New(formatErrorDescription(
Locale.ParseError(),
@@ -146,10 +146,10 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *SubSchema)
// definitions
if v, ok := m[KeyDefinitions]; ok {
switch mt := v.(type) {
case map[string]interface{}:
case map[string]any:
for _, dv := range mt {
switch dv.(type) {
case bool, map[string]interface{}:
case bool, map[string]any:
newSchema := &SubSchema{Property: KeyDefinitions, Parent: currentSchema}
err := d.parseSchema(dv, newSchema)
if err != nil {
@@ -203,7 +203,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *SubSchema)
if err != nil {
return err
}
case []interface{}:
case []any:
for _, typeInArray := range t {
s, isString := typeInArray.(string)
if !isString {
@@ -231,7 +231,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *SubSchema)
switch v := additionalProperties.(type) {
case bool:
currentSchema.additionalProperties = v
case map[string]interface{}:
case map[string]any:
newSchema := &SubSchema{Property: KeyAdditionalProperties, Parent: currentSchema, Ref: currentSchema.Ref}
currentSchema.additionalProperties = newSchema
err := d.parseSchema(v, newSchema)
@@ -270,7 +270,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *SubSchema)
// propertyNames
if propertyNames, found := m[KeyPropertyNames]; found && *currentSchema.Draft >= Draft6 {
switch propertyNames.(type) {
case bool, map[string]interface{}:
case bool, map[string]any:
newSchema := &SubSchema{Property: KeyPropertyNames, Parent: currentSchema, Ref: currentSchema.Ref}
currentSchema.propertyNames = newSchema
err := d.parseSchema(propertyNames, newSchema)
@@ -299,10 +299,10 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *SubSchema)
// items
if items, found := m[KeyItems]; found {
switch i := items.(type) {
case []interface{}:
case []any:
for _, itemElement := range i {
switch itemElement.(type) {
case map[string]interface{}, bool:
case map[string]any, bool:
newSchema := &SubSchema{Parent: currentSchema, Property: KeyItems}
newSchema.Ref = currentSchema.Ref
currentSchema.ItemsChildren = append(currentSchema.ItemsChildren, newSchema)
@@ -315,7 +315,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *SubSchema)
}
currentSchema.ItemsChildrenIsSingleSchema = false
}
case map[string]interface{}, bool:
case map[string]any, bool:
newSchema := &SubSchema{Parent: currentSchema, Property: KeyItems}
newSchema.Ref = currentSchema.Ref
currentSchema.ItemsChildren = append(currentSchema.ItemsChildren, newSchema)
@@ -334,7 +334,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *SubSchema)
switch i := additionalItems.(type) {
case bool:
currentSchema.additionalItems = i
case map[string]interface{}:
case map[string]any:
newSchema := &SubSchema{Property: KeyAdditionalItems, Parent: currentSchema, Ref: currentSchema.Ref}
currentSchema.additionalItems = newSchema
err := d.parseSchema(additionalItems, newSchema)
@@ -717,7 +717,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *SubSchema)
if vNot, found := m[KeyNot]; found {
switch vNot.(type) {
case bool, map[string]interface{}:
case bool, map[string]any:
newSchema := &SubSchema{Property: KeyNot, Parent: currentSchema, Ref: currentSchema.Ref}
currentSchema.not = newSchema
err := d.parseSchema(vNot, newSchema)
@@ -735,7 +735,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *SubSchema)
if *currentSchema.Draft >= Draft7 {
if vIf, found := m[KeyIf]; found {
switch vIf.(type) {
case bool, map[string]interface{}:
case bool, map[string]any:
newSchema := &SubSchema{Property: KeyIf, Parent: currentSchema, Ref: currentSchema.Ref}
currentSchema._if = newSchema
err := d.parseSchema(vIf, newSchema)
@@ -752,7 +752,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *SubSchema)
if then, found := m[KeyThen]; found {
switch then.(type) {
case bool, map[string]interface{}:
case bool, map[string]any:
newSchema := &SubSchema{Property: KeyThen, Parent: currentSchema, Ref: currentSchema.Ref}
currentSchema._then = newSchema
err := d.parseSchema(then, newSchema)
@@ -769,7 +769,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *SubSchema)
if vElse, found := m[KeyElse]; found {
switch vElse.(type) {
case bool, map[string]interface{}:
case bool, map[string]any:
newSchema := &SubSchema{Property: KeyElse, Parent: currentSchema, Ref: currentSchema.Ref}
currentSchema._else = newSchema
err := d.parseSchema(vElse, newSchema)
@@ -788,9 +788,9 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *SubSchema)
return nil
}
func (d *Schema) parseReference(_ interface{}, currentSchema *SubSchema) error {
func (d *Schema) parseReference(_ any, currentSchema *SubSchema) error {
var (
refdDocumentNode interface{}
refdDocumentNode any
dsp *schemaPoolDocument
err error
)
@@ -809,7 +809,7 @@ func (d *Schema) parseReference(_ interface{}, currentSchema *SubSchema) error {
newSchema.Draft = dsp.Draft
switch refdDocumentNode.(type) {
case bool, map[string]interface{}:
case bool, map[string]any:
// expected
default:
return errors.New(formatErrorDescription(
@@ -829,8 +829,8 @@ func (d *Schema) parseReference(_ interface{}, currentSchema *SubSchema) error {
}
func (d *Schema) parseProperties(documentNode interface{}, currentSchema *SubSchema) error {
m, isMap := documentNode.(map[string]interface{})
func (d *Schema) parseProperties(documentNode any, currentSchema *SubSchema) error {
m, isMap := documentNode.(map[string]any)
if !isMap {
return errors.New(formatErrorDescription(
Locale.MustBeOfType(),
@@ -851,19 +851,19 @@ func (d *Schema) parseProperties(documentNode interface{}, currentSchema *SubSch
return nil
}
func (d *Schema) parseDependencies(documentNode interface{}, currentSchema *SubSchema) error {
m, isMap := documentNode.(map[string]interface{})
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]interface{})
currentSchema.dependencies = make(map[string]any)
for k := range m {
switch values := m[k].(type) {
case []interface{}:
case []any:
var valuesToRegister []string
for _, value := range values {
str, isString := value.(string)
@@ -880,7 +880,7 @@ func (d *Schema) parseDependencies(documentNode interface{}, currentSchema *SubS
currentSchema.dependencies[k] = valuesToRegister
}
case bool, map[string]interface{}:
case bool, map[string]any:
depSchema := &SubSchema{Property: k, Parent: currentSchema, Ref: currentSchema.Ref}
err := d.parseSchema(m[k], depSchema)
if err != nil {
@@ -913,7 +913,7 @@ func invalidType(expected, given string) error {
))
}
func getString(m map[string]interface{}, key string) (*string, error) {
func getString(m map[string]any, key string) (*string, error) {
v, found := m[key]
if !found {
// not found
@@ -927,13 +927,13 @@ func getString(m map[string]interface{}, key string) (*string, error) {
return &s, nil
}
func getMap(m map[string]interface{}, key string) (map[string]interface{}, error) {
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]interface{})
s, isMap := v.(map[string]any)
if !isMap {
// wrong type
return nil, invalidType(StringSchema, key)
@@ -941,12 +941,12 @@ func getMap(m map[string]interface{}, key string) (map[string]interface{}, error
return s, nil
}
func getSlice(m map[string]interface{}, key string) ([]interface{}, error) {
func getSlice(m map[string]any, key string) ([]any, error) {
v, found := m[key]
if !found {
return nil, nil
}
s, isArray := v.([]interface{})
s, isArray := v.([]any)
if !isArray {
return nil, errors.New(formatErrorDescription(
Locale.MustBeOfAn(),
@@ -45,7 +45,7 @@ func NewSchemaLoader() *SchemaLoader {
return ps
}
func (sl *SchemaLoader) validateMetaschema(documentNode interface{}) error {
func (sl *SchemaLoader) validateMetaschema(documentNode any) error {
var (
schema string
@@ -158,7 +158,7 @@ func (sl *SchemaLoader) Compile(rootSchema JSONLoader) (*Schema, error) {
d.DocumentReference = ref
d.ReferencePool = newSchemaReferencePool()
var doc interface{}
var doc any
if ref.String() != "" {
// Get document from schema pool
spd, err := d.Pool.GetDocument(d.DocumentReference)
@@ -34,7 +34,7 @@ import (
)
type schemaPoolDocument struct {
Document interface{}
Document any
Draft *Draft
}
@@ -44,7 +44,7 @@ type schemaPool struct {
autoDetect *bool
}
func (p *schemaPool) parseReferences(document interface{}, ref gojsonreference.JsonReference, pooled bool) error {
func (p *schemaPool) parseReferences(document any, ref gojsonreference.JsonReference, pooled bool) error {
var (
draft *Draft
@@ -72,7 +72,7 @@ func (p *schemaPool) parseReferences(document interface{}, ref gojsonreference.J
return err
}
func (p *schemaPool) parseReferencesRecursive(document interface{}, ref gojsonreference.JsonReference, draft *Draft) error {
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
@@ -80,14 +80,14 @@ func (p *schemaPool) parseReferencesRecursive(document interface{}, ref gojsonre
// 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 []interface{}:
case []any:
for _, v := range m {
err := p.parseReferencesRecursive(v, ref, draft)
if err != nil {
return err
}
}
case map[string]interface{}:
case map[string]any:
localRef := &ref
keyID := KeyIDNew
@@ -129,7 +129,7 @@ func (p *schemaPool) parseReferencesRecursive(document interface{}, ref gojsonre
// 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]interface{}); ok {
if child, ok := v.(map[string]any); ok {
for _, v := range child {
err := p.parseReferencesRecursive(v, *localRef, draft)
if err != nil {
@@ -28,6 +28,7 @@ package gojsonschema
import (
"errors"
"fmt"
"slices"
"strings"
)
@@ -58,13 +59,7 @@ func (t *jsonSchemaType) Add(etype string) error {
func (t *jsonSchemaType) Contains(etype string) bool {
for _, v := range t.types {
if v == etype {
return true
}
}
return false
return slices.Contains(t.types, etype)
}
func (t *jsonSchemaType) String() string {
@@ -123,8 +123,8 @@ type SubSchema struct {
maxProperties *int
required []string
dependencies map[string]interface{}
additionalProperties interface{}
dependencies map[string]any
additionalProperties any
patternProperties map[string]*SubSchema
propertyNames *SubSchema
@@ -134,7 +134,7 @@ type SubSchema struct {
uniqueItems bool
contains *SubSchema
additionalItems interface{}
additionalItems any
// validation : all
_const *string //const is a golang keyword
+14 -18
View File
@@ -29,18 +29,14 @@ package gojsonschema
import (
"encoding/json"
"math/big"
"slices"
)
func isStringInSlice(s []string, what string) bool {
for i := range s {
if s[i] == what {
return true
}
}
return false
return slices.Contains(s, what)
}
func marshalToJSONString(value interface{}) (*string, error) {
func marshalToJSONString(value any) (*string, error) {
mBytes, err := json.Marshal(value)
if err != nil {
@@ -51,7 +47,7 @@ func marshalToJSONString(value interface{}) (*string, error) {
return &sBytes, nil
}
func marshalWithoutNumber(value interface{}) (*string, error) {
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
@@ -63,7 +59,7 @@ func marshalWithoutNumber(value interface{}) (*string, error) {
return nil, err
}
var document interface{}
var document any
err = json.Unmarshal([]byte(*jsonString), &document)
if err != nil {
@@ -73,7 +69,7 @@ func marshalWithoutNumber(value interface{}) (*string, error) {
return marshalToJSONString(document)
}
func isJSONNumber(what interface{}) bool {
func isJSONNumber(what any) bool {
switch what.(type) {
@@ -84,7 +80,7 @@ func isJSONNumber(what interface{}) bool {
return false
}
func checkJSONInteger(what interface{}) (isInt bool) {
func checkJSONInteger(what any) (isInt bool) {
jsonNumber := what.(json.Number)
@@ -100,7 +96,7 @@ const (
minJSONFloat = -float64(1<<53 - 1) //-9007199254740991.0 -2^53 - 1
)
func mustBeInteger(what interface{}) *int {
func mustBeInteger(what any) *int {
number, ok := what.(json.Number)
if !ok {
return nil
@@ -123,7 +119,7 @@ func mustBeInteger(what interface{}) *int {
return &int32Value
}
func mustBeNumber(what interface{}) *big.Rat {
func mustBeNumber(what any) *big.Rat {
number, ok := what.(json.Number)
if !ok {
return nil
@@ -136,11 +132,11 @@ func mustBeNumber(what interface{}) *big.Rat {
return nil
}
func convertDocumentNode(val interface{}) interface{} {
func convertDocumentNode(val any) any {
if lval, ok := val.([]interface{}); ok {
if lval, ok := val.([]any); ok {
res := []interface{}{}
res := []any{}
for _, v := range lval {
res = append(res, convertDocumentNode(v))
}
@@ -149,9 +145,9 @@ func convertDocumentNode(val interface{}) interface{} {
}
if mval, ok := val.(map[interface{}]interface{}); ok {
if mval, ok := val.(map[any]any); ok {
res := map[string]interface{}{}
res := map[string]any{}
for k, v := range mval {
res[k.(string)] = convertDocumentNode(v)
+15 -15
View File
@@ -54,21 +54,21 @@ func (v *Schema) Validate(l JSONLoader) (*Result, error) {
return v.validateDocument(root), nil
}
func (v *Schema) validateDocument(root interface{}) *Result {
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 interface{}, context *JSONContext) *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 interface{}, result *Result, context *JSONContext) {
func (v *SubSchema) validateRecursive(currentSubSchema *SubSchema, currentNode any, result *Result, context *JSONContext) {
if internalLogEnabled {
internalLog("validateRecursive %s", context.String())
@@ -167,7 +167,7 @@ func (v *SubSchema) validateRecursive(currentSubSchema *SubSchema, currentNode i
return
}
castCurrentNode := currentNode.([]interface{})
castCurrentNode := currentNode.([]any)
currentSubSchema.validateSchema(currentSubSchema, castCurrentNode, result, context)
@@ -190,9 +190,9 @@ func (v *SubSchema) validateRecursive(currentSubSchema *SubSchema, currentNode i
return
}
castCurrentNode, ok := currentNode.(map[string]interface{})
castCurrentNode, ok := currentNode.(map[string]any)
if !ok {
castCurrentNode = convertDocumentNode(currentNode).(map[string]interface{})
castCurrentNode = convertDocumentNode(currentNode).(map[string]any)
}
currentSubSchema.validateSchema(currentSubSchema, castCurrentNode, result, context)
@@ -264,7 +264,7 @@ func (v *SubSchema) validateRecursive(currentSubSchema *SubSchema, currentNode i
}
// Different kinds of validation there, SubSchema / common / array / object / string...
func (v *SubSchema) validateSchema(currentSubSchema *SubSchema, currentNode interface{}, result *Result, context *JSONContext) {
func (v *SubSchema) validateSchema(currentSubSchema *SubSchema, currentNode any, result *Result, context *JSONContext) {
if internalLogEnabled {
internalLog("validateSchema %s", context.String())
@@ -349,14 +349,14 @@ func (v *SubSchema) validateSchema(currentSubSchema *SubSchema, currentNode inte
}
if len(currentSubSchema.dependencies) > 0 {
if currentNodeMap, ok := currentNode.(map[string]interface{}); ok {
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]interface{})[dependOnKey]; !dependencyResolved {
if _, dependencyResolved := currentNode.(map[string]any)[dependOnKey]; !dependencyResolved {
result.addInternalError(
new(MissingDependencyError),
context,
@@ -395,7 +395,7 @@ func (v *SubSchema) validateSchema(currentSubSchema *SubSchema, currentNode inte
result.incrementScore()
}
func (v *SubSchema) validateCommon(currentSubSchema *SubSchema, value interface{}, result *Result, context *JSONContext) {
func (v *SubSchema) validateCommon(currentSubSchema *SubSchema, value any, result *Result, context *JSONContext) {
if internalLogEnabled {
internalLog("validateCommon %s", context.String())
@@ -452,7 +452,7 @@ func (v *SubSchema) validateCommon(currentSubSchema *SubSchema, value interface{
result.incrementScore()
}
func (v *SubSchema) validateArray(currentSubSchema *SubSchema, value []interface{}, result *Result, context *JSONContext) {
func (v *SubSchema) validateArray(currentSubSchema *SubSchema, value []any, result *Result, context *JSONContext) {
if internalLogEnabled {
internalLog("validateArray %s", context.String())
@@ -578,7 +578,7 @@ func (v *SubSchema) validateArray(currentSubSchema *SubSchema, value []interface
result.incrementScore()
}
func (v *SubSchema) validateObject(currentSubSchema *SubSchema, value map[string]interface{}, result *Result, context *JSONContext) {
func (v *SubSchema) validateObject(currentSubSchema *SubSchema, value map[string]any, result *Result, context *JSONContext) {
if internalLogEnabled {
internalLog("validateObject %s", context.String())
@@ -675,7 +675,7 @@ func (v *SubSchema) validateObject(currentSubSchema *SubSchema, value map[string
result.incrementScore()
}
func (v *SubSchema) validatePatternProperty(currentSubSchema *SubSchema, key string, value interface{}, result *Result, context *JSONContext) bool {
func (v *SubSchema) validatePatternProperty(currentSubSchema *SubSchema, key string, value any, result *Result, context *JSONContext) bool {
if internalLogEnabled {
internalLog("validatePatternProperty %s", context.String())
@@ -701,7 +701,7 @@ func (v *SubSchema) validatePatternProperty(currentSubSchema *SubSchema, key str
return true
}
func (v *SubSchema) validateString(currentSubSchema *SubSchema, value interface{}, result *Result, context *JSONContext) {
func (v *SubSchema) validateString(currentSubSchema *SubSchema, value any, result *Result, context *JSONContext) {
// Ignore JSON numbers
stringValue, isString := value.(string)
@@ -752,7 +752,7 @@ func (v *SubSchema) validateString(currentSubSchema *SubSchema, value interface{
result.incrementScore()
}
func (v *SubSchema) validateNumber(currentSubSchema *SubSchema, value interface{}, result *Result, context *JSONContext) {
func (v *SubSchema) validateNumber(currentSubSchema *SubSchema, value any, result *Result, context *JSONContext) {
// Ignore non numbers
number, isNumber := value.(json.Number)
-19
View File
@@ -1,19 +0,0 @@
Copyright (c) 2018 Adam Scarr
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -1,37 +0,0 @@
package ast
func arg2map(defs ArgumentDefinitionList, args ArgumentList, vars map[string]interface{}) map[string]interface{} {
result := map[string]interface{}{}
var err error
for _, argDef := range defs {
var val interface{}
var hasValue bool
if argValue := args.ForName(argDef.Name); argValue != nil {
if argValue.Value.Kind == Variable {
val, hasValue = vars[argValue.Value.Raw]
} else {
val, err = argValue.Value.Value(vars)
if err != nil {
panic(err)
}
hasValue = true
}
}
if !hasValue && argDef.DefaultValue != nil {
val, err = argDef.DefaultValue.Value(vars)
if err != nil {
panic(err)
}
hasValue = true
}
if hasValue {
result[argDef.Name] = val
}
}
return result
}
@@ -1,148 +0,0 @@
package ast
type FieldList []*FieldDefinition
func (l FieldList) ForName(name string) *FieldDefinition {
for _, it := range l {
if it.Name == name {
return it
}
}
return nil
}
type EnumValueList []*EnumValueDefinition
func (l EnumValueList) ForName(name string) *EnumValueDefinition {
for _, it := range l {
if it.Name == name {
return it
}
}
return nil
}
type DirectiveList []*Directive
func (l DirectiveList) ForName(name string) *Directive {
for _, it := range l {
if it.Name == name {
return it
}
}
return nil
}
func (l DirectiveList) ForNames(name string) []*Directive {
resp := []*Directive{}
for _, it := range l {
if it.Name == name {
resp = append(resp, it)
}
}
return resp
}
type OperationList []*OperationDefinition
func (l OperationList) ForName(name string) *OperationDefinition {
if name == "" && len(l) == 1 {
return l[0]
}
for _, it := range l {
if it.Name == name {
return it
}
}
return nil
}
type FragmentDefinitionList []*FragmentDefinition
func (l FragmentDefinitionList) ForName(name string) *FragmentDefinition {
for _, it := range l {
if it.Name == name {
return it
}
}
return nil
}
type VariableDefinitionList []*VariableDefinition
func (l VariableDefinitionList) ForName(name string) *VariableDefinition {
for _, it := range l {
if it.Variable == name {
return it
}
}
return nil
}
type ArgumentList []*Argument
func (l ArgumentList) ForName(name string) *Argument {
for _, it := range l {
if it.Name == name {
return it
}
}
return nil
}
type ArgumentDefinitionList []*ArgumentDefinition
func (l ArgumentDefinitionList) ForName(name string) *ArgumentDefinition {
for _, it := range l {
if it.Name == name {
return it
}
}
return nil
}
type SchemaDefinitionList []*SchemaDefinition
type DirectiveDefinitionList []*DirectiveDefinition
func (l DirectiveDefinitionList) ForName(name string) *DirectiveDefinition {
for _, it := range l {
if it.Name == name {
return it
}
}
return nil
}
type DefinitionList []*Definition
func (l DefinitionList) ForName(name string) *Definition {
for _, it := range l {
if it.Name == name {
return it
}
}
return nil
}
type OperationTypeDefinitionList []*OperationTypeDefinition
func (l OperationTypeDefinitionList) ForType(name string) *OperationTypeDefinition {
for _, it := range l {
if it.Type == name {
return it
}
}
return nil
}
type ChildValueList []*ChildValue
func (v ChildValueList) ForName(name string) *Value {
for _, f := range v {
if f.Name == name {
return f.Value
}
}
return nil
}
@@ -1,216 +0,0 @@
package ast
import (
"encoding/json"
)
func UnmarshalSelectionSet(b []byte) (SelectionSet, error) {
var tmp []json.RawMessage
if err := json.Unmarshal(b, &tmp); err != nil {
return nil, err
}
var result = make([]Selection, 0)
for _, item := range tmp {
var field Field
if err := json.Unmarshal(item, &field); err == nil {
result = append(result, &field)
continue
}
var fragmentSpread FragmentSpread
if err := json.Unmarshal(item, &fragmentSpread); err == nil {
result = append(result, &fragmentSpread)
continue
}
var inlineFragment InlineFragment
if err := json.Unmarshal(item, &inlineFragment); err == nil {
result = append(result, &inlineFragment)
continue
}
}
return result, nil
}
func (f *FragmentDefinition) UnmarshalJSON(b []byte) error {
var tmp map[string]json.RawMessage
if err := json.Unmarshal(b, &tmp); err != nil {
return err
}
for k := range tmp {
switch k {
case "Name":
err := json.Unmarshal(tmp[k], &f.Name)
if err != nil {
return err
}
case "VariableDefinition":
err := json.Unmarshal(tmp[k], &f.VariableDefinition)
if err != nil {
return err
}
case "TypeCondition":
err := json.Unmarshal(tmp[k], &f.TypeCondition)
if err != nil {
return err
}
case "Directives":
err := json.Unmarshal(tmp[k], &f.Directives)
if err != nil {
return err
}
case "SelectionSet":
ss, err := UnmarshalSelectionSet(tmp[k])
if err != nil {
return err
}
f.SelectionSet = ss
case "Definition":
err := json.Unmarshal(tmp[k], &f.Definition)
if err != nil {
return err
}
case "Position":
err := json.Unmarshal(tmp[k], &f.Position)
if err != nil {
return err
}
}
}
return nil
}
func (f *InlineFragment) UnmarshalJSON(b []byte) error {
var tmp map[string]json.RawMessage
if err := json.Unmarshal(b, &tmp); err != nil {
return err
}
for k := range tmp {
switch k {
case "TypeCondition":
err := json.Unmarshal(tmp[k], &f.TypeCondition)
if err != nil {
return err
}
case "Directives":
err := json.Unmarshal(tmp[k], &f.Directives)
if err != nil {
return err
}
case "SelectionSet":
ss, err := UnmarshalSelectionSet(tmp[k])
if err != nil {
return err
}
f.SelectionSet = ss
case "ObjectDefinition":
err := json.Unmarshal(tmp[k], &f.ObjectDefinition)
if err != nil {
return err
}
case "Position":
err := json.Unmarshal(tmp[k], &f.Position)
if err != nil {
return err
}
}
}
return nil
}
func (f *OperationDefinition) UnmarshalJSON(b []byte) error {
var tmp map[string]json.RawMessage
if err := json.Unmarshal(b, &tmp); err != nil {
return err
}
for k := range tmp {
switch k {
case "Operation":
err := json.Unmarshal(tmp[k], &f.Operation)
if err != nil {
return err
}
case "Name":
err := json.Unmarshal(tmp[k], &f.Name)
if err != nil {
return err
}
case "VariableDefinitions":
err := json.Unmarshal(tmp[k], &f.VariableDefinitions)
if err != nil {
return err
}
case "Directives":
err := json.Unmarshal(tmp[k], &f.Directives)
if err != nil {
return err
}
case "SelectionSet":
ss, err := UnmarshalSelectionSet(tmp[k])
if err != nil {
return err
}
f.SelectionSet = ss
case "Position":
err := json.Unmarshal(tmp[k], &f.Position)
if err != nil {
return err
}
}
}
return nil
}
func (f *Field) UnmarshalJSON(b []byte) error {
var tmp map[string]json.RawMessage
if err := json.Unmarshal(b, &tmp); err != nil {
return err
}
for k := range tmp {
switch k {
case "Alias":
err := json.Unmarshal(tmp[k], &f.Alias)
if err != nil {
return err
}
case "Name":
err := json.Unmarshal(tmp[k], &f.Name)
if err != nil {
return err
}
case "Arguments":
err := json.Unmarshal(tmp[k], &f.Arguments)
if err != nil {
return err
}
case "Directives":
err := json.Unmarshal(tmp[k], &f.Directives)
if err != nil {
return err
}
case "SelectionSet":
ss, err := UnmarshalSelectionSet(tmp[k])
if err != nil {
return err
}
f.SelectionSet = ss
case "Position":
err := json.Unmarshal(tmp[k], &f.Position)
if err != nil {
return err
}
case "Definition":
err := json.Unmarshal(tmp[k], &f.Definition)
if err != nil {
return err
}
case "ObjectDefinition":
err := json.Unmarshal(tmp[k], &f.ObjectDefinition)
if err != nil {
return err
}
}
}
return nil
}
@@ -1,94 +0,0 @@
package ast
type DefinitionKind string
const (
Scalar DefinitionKind = "SCALAR"
Object DefinitionKind = "OBJECT"
Interface DefinitionKind = "INTERFACE"
Union DefinitionKind = "UNION"
Enum DefinitionKind = "ENUM"
InputObject DefinitionKind = "INPUT_OBJECT"
)
// Definition is the core type definition object, it includes all of the definable types
// but does *not* cover schema or directives.
//
// @vektah: Javascript implementation has different types for all of these, but they are
// more similar than different and don't define any behaviour. I think this style of
// "some hot" struct works better, at least for go.
//
// Type extensions are also represented by this same struct.
type Definition struct {
Kind DefinitionKind
Description string
Name string
Directives DirectiveList
Interfaces []string // object and input object
Fields FieldList // object and input object
Types []string // union
EnumValues EnumValueList // enum
Position *Position `dump:"-" json:"-"`
BuiltIn bool `dump:"-"`
}
func (d *Definition) IsLeafType() bool {
return d.Kind == Enum || d.Kind == Scalar
}
func (d *Definition) IsAbstractType() bool {
return d.Kind == Interface || d.Kind == Union
}
func (d *Definition) IsCompositeType() bool {
return d.Kind == Object || d.Kind == Interface || d.Kind == Union
}
func (d *Definition) IsInputType() bool {
return d.Kind == Scalar || d.Kind == Enum || d.Kind == InputObject
}
func (d *Definition) OneOf(types ...string) bool {
for _, t := range types {
if d.Name == t {
return true
}
}
return false
}
type FieldDefinition struct {
Description string
Name string
Arguments ArgumentDefinitionList // only for objects
DefaultValue *Value // only for input objects
Type *Type
Directives DirectiveList
Position *Position `dump:"-" json:"-"`
}
type ArgumentDefinition struct {
Description string
Name string
DefaultValue *Value
Type *Type
Directives DirectiveList
Position *Position `dump:"-" json:"-"`
}
type EnumValueDefinition struct {
Description string
Name string
Directives DirectiveList
Position *Position `dump:"-" json:"-"`
}
type DirectiveDefinition struct {
Description string
Name string
Arguments ArgumentDefinitionList
Locations []DirectiveLocation
IsRepeatable bool
Position *Position `dump:"-" json:"-"`
}
@@ -1,43 +0,0 @@
package ast
type DirectiveLocation string
const (
// Executable
LocationQuery DirectiveLocation = `QUERY`
LocationMutation DirectiveLocation = `MUTATION`
LocationSubscription DirectiveLocation = `SUBSCRIPTION`
LocationField DirectiveLocation = `FIELD`
LocationFragmentDefinition DirectiveLocation = `FRAGMENT_DEFINITION`
LocationFragmentSpread DirectiveLocation = `FRAGMENT_SPREAD`
LocationInlineFragment DirectiveLocation = `INLINE_FRAGMENT`
// Type System
LocationSchema DirectiveLocation = `SCHEMA`
LocationScalar DirectiveLocation = `SCALAR`
LocationObject DirectiveLocation = `OBJECT`
LocationFieldDefinition DirectiveLocation = `FIELD_DEFINITION`
LocationArgumentDefinition DirectiveLocation = `ARGUMENT_DEFINITION`
LocationInterface DirectiveLocation = `INTERFACE`
LocationUnion DirectiveLocation = `UNION`
LocationEnum DirectiveLocation = `ENUM`
LocationEnumValue DirectiveLocation = `ENUM_VALUE`
LocationInputObject DirectiveLocation = `INPUT_OBJECT`
LocationInputFieldDefinition DirectiveLocation = `INPUT_FIELD_DEFINITION`
LocationVariableDefinition DirectiveLocation = `VARIABLE_DEFINITION`
)
type Directive struct {
Name string
Arguments ArgumentList
Position *Position `dump:"-" json:"-"`
// Requires validation
ParentDefinition *Definition
Definition *DirectiveDefinition
Location DirectiveLocation
}
func (d *Directive) ArgumentMap(vars map[string]interface{}) map[string]interface{} {
return arg2map(d.Definition.Arguments, d.Arguments, vars)
}
@@ -1,79 +0,0 @@
package ast
type QueryDocument struct {
Operations OperationList
Fragments FragmentDefinitionList
Position *Position `dump:"-" json:"-"`
}
type SchemaDocument struct {
Schema SchemaDefinitionList
SchemaExtension SchemaDefinitionList
Directives DirectiveDefinitionList
Definitions DefinitionList
Extensions DefinitionList
Position *Position `dump:"-" json:"-"`
}
func (d *SchemaDocument) Merge(other *SchemaDocument) {
d.Schema = append(d.Schema, other.Schema...)
d.SchemaExtension = append(d.SchemaExtension, other.SchemaExtension...)
d.Directives = append(d.Directives, other.Directives...)
d.Definitions = append(d.Definitions, other.Definitions...)
d.Extensions = append(d.Extensions, other.Extensions...)
}
type Schema struct {
Query *Definition
Mutation *Definition
Subscription *Definition
Types map[string]*Definition
Directives map[string]*DirectiveDefinition
PossibleTypes map[string][]*Definition
Implements map[string][]*Definition
Description string
}
// AddTypes is the helper to add types definition to the schema
func (s *Schema) AddTypes(defs ...*Definition) {
if s.Types == nil {
s.Types = make(map[string]*Definition)
}
for _, def := range defs {
s.Types[def.Name] = def
}
}
func (s *Schema) AddPossibleType(name string, def *Definition) {
s.PossibleTypes[name] = append(s.PossibleTypes[name], def)
}
// GetPossibleTypes will enumerate all the definitions for a given interface or union
func (s *Schema) GetPossibleTypes(def *Definition) []*Definition {
return s.PossibleTypes[def.Name]
}
func (s *Schema) AddImplements(name string, iface *Definition) {
s.Implements[name] = append(s.Implements[name], iface)
}
// GetImplements returns all the interface and union definitions that the given definition satisfies
func (s *Schema) GetImplements(def *Definition) []*Definition {
return s.Implements[def.Name]
}
type SchemaDefinition struct {
Description string
Directives DirectiveList
OperationTypes OperationTypeDefinitionList
Position *Position `dump:"-" json:"-"`
}
type OperationTypeDefinition struct {
Operation Operation
Type string
Position *Position `dump:"-" json:"-"`
}
@@ -1,159 +0,0 @@
package ast
import (
"bytes"
"fmt"
"reflect"
"strconv"
"strings"
)
// Dump turns ast into a stable string format for assertions in tests
func Dump(i interface{}) string {
v := reflect.ValueOf(i)
d := dumper{Buffer: &bytes.Buffer{}}
d.dump(v)
return d.String()
}
type dumper struct {
*bytes.Buffer
indent int
}
type Dumpable interface {
Dump() string
}
func (d *dumper) dump(v reflect.Value) {
if dumpable, isDumpable := v.Interface().(Dumpable); isDumpable {
d.WriteString(dumpable.Dump())
return
}
switch v.Kind() {
case reflect.Bool:
if v.Bool() {
d.WriteString("true")
} else {
d.WriteString("false")
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
d.WriteString(strconv.FormatInt(v.Int(), 10))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
d.WriteString(strconv.FormatUint(v.Uint(), 10))
case reflect.Float32, reflect.Float64:
d.WriteString(fmt.Sprintf("%.2f", v.Float()))
case reflect.String:
if v.Type().Name() != "string" {
d.WriteString(v.Type().Name() + "(" + strconv.Quote(v.String()) + ")")
} else {
d.WriteString(strconv.Quote(v.String()))
}
case reflect.Array, reflect.Slice:
d.dumpArray(v)
case reflect.Interface, reflect.Ptr:
d.dumpPtr(v)
case reflect.Struct:
d.dumpStruct(v)
default:
panic(fmt.Errorf("unsupported kind: %s\n buf: %s", v.Kind().String(), d.String()))
}
}
func (d *dumper) writeIndent() {
d.Buffer.WriteString(strings.Repeat(" ", d.indent))
}
func (d *dumper) nl() {
d.Buffer.WriteByte('\n')
d.writeIndent()
}
func typeName(t reflect.Type) string {
if t.Kind() == reflect.Ptr {
return typeName(t.Elem())
}
return t.Name()
}
func (d *dumper) dumpArray(v reflect.Value) {
d.WriteString("[" + typeName(v.Type().Elem()) + "]")
for i := range v.Len() {
d.nl()
d.WriteString("- ")
d.indent++
d.dump(v.Index(i))
d.indent--
}
}
func (d *dumper) dumpStruct(v reflect.Value) {
d.WriteString("<" + v.Type().Name() + ">")
d.indent++
typ := v.Type()
for i := range v.NumField() {
f := v.Field(i)
if typ.Field(i).Tag.Get("dump") == "-" {
continue
}
if isZero(f) {
continue
}
d.nl()
d.WriteString(typ.Field(i).Name)
d.WriteString(": ")
d.dump(v.Field(i))
}
d.indent--
}
func isZero(v reflect.Value) bool {
switch v.Kind() {
case reflect.Ptr, reflect.Interface:
return v.IsNil()
case reflect.Func, reflect.Map:
return v.IsNil()
case reflect.Array, reflect.Slice:
if v.IsNil() {
return true
}
z := true
for i := range v.Len() {
z = z && isZero(v.Index(i))
}
return z
case reflect.Struct:
z := true
for i := range v.NumField() {
z = z && isZero(v.Field(i))
}
return z
case reflect.String:
return v.String() == ""
}
// Compare other types directly:
return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()))
}
func (d *dumper) dumpPtr(v reflect.Value) {
if v.IsNil() {
d.WriteString("nil")
return
}
d.dump(v.Elem())
}
@@ -1,38 +0,0 @@
package ast
type FragmentSpread struct {
Name string
Directives DirectiveList
// Require validation
ObjectDefinition *Definition
Definition *FragmentDefinition
Position *Position `dump:"-" json:"-"`
}
type InlineFragment struct {
TypeCondition string
Directives DirectiveList
SelectionSet SelectionSet
// Require validation
ObjectDefinition *Definition
Position *Position `dump:"-" json:"-"`
}
type FragmentDefinition struct {
Name string
// Note: fragment variable definitions are experimental and may be changed
// or removed in the future.
VariableDefinition VariableDefinitionList
TypeCondition string
Directives DirectiveList
SelectionSet SelectionSet
// Require validation
Definition *Definition
Position *Position `dump:"-" json:"-"`
}
@@ -1,30 +0,0 @@
package ast
type Operation string
const (
Query Operation = "query"
Mutation Operation = "mutation"
Subscription Operation = "subscription"
)
type OperationDefinition struct {
Operation Operation
Name string
VariableDefinitions VariableDefinitionList
Directives DirectiveList
SelectionSet SelectionSet
Position *Position `dump:"-" json:"-"`
}
type VariableDefinition struct {
Variable string
Type *Type
DefaultValue *Value
Directives DirectiveList
Position *Position `dump:"-" json:"-"`
// Requires validation
Definition *Definition
Used bool `dump:"-"`
}
-67
View File
@@ -1,67 +0,0 @@
package ast
import (
"bytes"
"encoding/json"
"fmt"
)
var _ json.Unmarshaler = (*Path)(nil)
type Path []PathElement
type PathElement interface {
isPathElement()
}
var _ PathElement = PathIndex(0)
var _ PathElement = PathName("")
func (path Path) String() string {
var str bytes.Buffer
for i, v := range path {
switch v := v.(type) {
case PathIndex:
str.WriteString(fmt.Sprintf("[%d]", v))
case PathName:
if i != 0 {
str.WriteByte('.')
}
str.WriteString(string(v))
default:
panic(fmt.Sprintf("unknown type: %T", v))
}
}
return str.String()
}
func (path *Path) UnmarshalJSON(b []byte) error {
var vs []interface{}
err := json.Unmarshal(b, &vs)
if err != nil {
return err
}
*path = make([]PathElement, 0, len(vs))
for _, v := range vs {
switch v := v.(type) {
case string:
*path = append(*path, PathName(v))
case int:
*path = append(*path, PathIndex(v))
case float64:
*path = append(*path, PathIndex(int(v)))
default:
return fmt.Errorf("unknown path element type: %T", v)
}
}
return nil
}
type PathIndex int
func (PathIndex) isPathElement() {}
type PathName string
func (PathName) isPathElement() {}
@@ -1,39 +0,0 @@
package ast
type SelectionSet []Selection
type Selection interface {
isSelection()
GetPosition() *Position
}
func (*Field) isSelection() {}
func (*FragmentSpread) isSelection() {}
func (*InlineFragment) isSelection() {}
func (s *Field) GetPosition() *Position { return s.Position }
func (s *FragmentSpread) GetPosition() *Position { return s.Position }
func (s *InlineFragment) GetPosition() *Position { return s.Position }
type Field struct {
Alias string
Name string
Arguments ArgumentList
Directives DirectiveList
SelectionSet SelectionSet
Position *Position `dump:"-" json:"-"`
// Require validation
Definition *FieldDefinition
ObjectDefinition *Definition
}
type Argument struct {
Name string
Value *Value
Position *Position `dump:"-" json:"-"`
}
func (s *Field) ArgumentMap(vars map[string]interface{}) map[string]interface{} {
return arg2map(s.Definition.Arguments, s.Arguments, vars)
}
@@ -1,19 +0,0 @@
package ast
// Source covers a single *.graphql file
type Source struct {
// Name is the filename of the source
Name string
// Input is the actual contents of the source file
Input string
// BuiltIn indicate whether the source is a part of the specification
BuiltIn bool
}
type Position struct {
Start int // The starting position, in runes, of this token in the input.
End int // The end position, in runes, of this token in the input.
Line int // The line number at the start of this item.
Column int // The column number at the start of this item.
Src *Source // The source document this token belongs to
}
-68
View File
@@ -1,68 +0,0 @@
package ast
func NonNullNamedType(named string, pos *Position) *Type {
return &Type{NamedType: named, NonNull: true, Position: pos}
}
func NamedType(named string, pos *Position) *Type {
return &Type{NamedType: named, NonNull: false, Position: pos}
}
func NonNullListType(elem *Type, pos *Position) *Type {
return &Type{Elem: elem, NonNull: true, Position: pos}
}
func ListType(elem *Type, pos *Position) *Type {
return &Type{Elem: elem, NonNull: false, Position: pos}
}
type Type struct {
NamedType string
Elem *Type
NonNull bool
Position *Position `dump:"-" json:"-"`
}
func (t *Type) Name() string {
if t.NamedType != "" {
return t.NamedType
}
return t.Elem.Name()
}
func (t *Type) String() string {
nn := ""
if t.NonNull {
nn = "!"
}
if t.NamedType != "" {
return t.NamedType + nn
}
return "[" + t.Elem.String() + "]" + nn
}
func (t *Type) IsCompatible(other *Type) bool {
if t.NamedType != other.NamedType {
return false
}
if t.Elem != nil && other.Elem == nil {
return false
}
if t.Elem != nil && !t.Elem.IsCompatible(other.Elem) {
return false
}
if other.NonNull {
return t.NonNull
}
return true
}
func (t *Type) Dump() string {
return t.String()
}
-120
View File
@@ -1,120 +0,0 @@
package ast
import (
"fmt"
"strconv"
"strings"
)
type ValueKind int
const (
Variable ValueKind = iota
IntValue
FloatValue
StringValue
BlockValue
BooleanValue
NullValue
EnumValue
ListValue
ObjectValue
)
type Value struct {
Raw string
Children ChildValueList
Kind ValueKind
Position *Position `dump:"-" json:"-"`
// Require validation
Definition *Definition
VariableDefinition *VariableDefinition
ExpectedType *Type
}
type ChildValue struct {
Name string
Value *Value
Position *Position `dump:"-" json:"-"`
}
func (v *Value) Value(vars map[string]interface{}) (interface{}, error) {
if v == nil {
return nil, nil
}
switch v.Kind {
case Variable:
if value, ok := vars[v.Raw]; ok {
return value, nil
}
if v.VariableDefinition != nil && v.VariableDefinition.DefaultValue != nil {
return v.VariableDefinition.DefaultValue.Value(vars)
}
return nil, nil
case IntValue:
return strconv.ParseInt(v.Raw, 10, 64)
case FloatValue:
return strconv.ParseFloat(v.Raw, 64)
case StringValue, BlockValue, EnumValue:
return v.Raw, nil
case BooleanValue:
return strconv.ParseBool(v.Raw)
case NullValue:
return nil, nil
case ListValue:
var val []interface{}
for _, elem := range v.Children {
elemVal, err := elem.Value.Value(vars)
if err != nil {
return val, err
}
val = append(val, elemVal)
}
return val, nil
case ObjectValue:
val := map[string]interface{}{}
for _, elem := range v.Children {
elemVal, err := elem.Value.Value(vars)
if err != nil {
return val, err
}
val[elem.Name] = elemVal
}
return val, nil
default:
panic(fmt.Errorf("unknown value kind %d", v.Kind))
}
}
func (v *Value) String() string {
if v == nil {
return "<nil>"
}
switch v.Kind {
case Variable:
return "$" + v.Raw
case IntValue, FloatValue, EnumValue, BooleanValue, NullValue:
return v.Raw
case StringValue, BlockValue:
return strconv.Quote(v.Raw)
case ListValue:
var val []string
for _, elem := range v.Children {
val = append(val, elem.Value.String())
}
return "[" + strings.Join(val, ",") + "]"
case ObjectValue:
var val []string
for _, elem := range v.Children {
val = append(val, elem.Name+":"+elem.Value.String())
}
return "{" + strings.Join(val, ",") + "}"
default:
panic(fmt.Errorf("unknown value kind %d", v.Kind))
}
}
func (v *Value) Dump() string {
return v.String()
}
@@ -1,147 +0,0 @@
package gqlerror
import (
"bytes"
"errors"
"fmt"
"strconv"
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
)
// Error is the standard graphql error type described in https://facebook.github.io/graphql/draft/#sec-Errors
type Error struct {
err error `json:"-"`
Message string `json:"message"`
Path ast.Path `json:"path,omitempty"`
Locations []Location `json:"locations,omitempty"`
Extensions map[string]interface{} `json:"extensions,omitempty"`
Rule string `json:"-"`
}
func (err *Error) SetFile(file string) {
if file == "" {
return
}
if err.Extensions == nil {
err.Extensions = map[string]interface{}{}
}
err.Extensions["file"] = file
}
type Location struct {
Line int `json:"line,omitempty"`
Column int `json:"column,omitempty"`
}
type List []*Error
func (err *Error) Error() string {
var res bytes.Buffer
if err == nil {
return ""
}
filename, _ := err.Extensions["file"].(string)
if filename == "" {
filename = "input"
}
res.WriteString(filename)
if len(err.Locations) > 0 {
res.WriteByte(':')
res.WriteString(strconv.Itoa(err.Locations[0].Line))
res.WriteByte(':')
res.WriteString(strconv.Itoa(err.Locations[0].Column))
}
res.WriteString(": ")
if ps := err.pathString(); ps != "" {
res.WriteString(ps)
res.WriteByte(' ')
}
res.WriteString(err.Message)
return res.String()
}
func (err Error) pathString() string {
return err.Path.String()
}
func (err Error) Unwrap() error {
return err.err
}
func (errs List) Error() string {
var buf bytes.Buffer
for _, err := range errs {
buf.WriteString(err.Error())
buf.WriteByte('\n')
}
return buf.String()
}
func (errs List) Is(target error) bool {
for _, err := range errs {
if errors.Is(err, target) {
return true
}
}
return false
}
func (errs List) As(target interface{}) bool {
for _, err := range errs {
if errors.As(err, target) {
return true
}
}
return false
}
func WrapPath(path ast.Path, err error) *Error {
return &Error{
err: err,
Message: err.Error(),
Path: path,
}
}
func Errorf(message string, args ...interface{}) *Error {
return &Error{
Message: fmt.Sprintf(message, args...),
}
}
func ErrorPathf(path ast.Path, message string, args ...interface{}) *Error {
return &Error{
Message: fmt.Sprintf(message, args...),
Path: path,
}
}
func ErrorPosf(pos *ast.Position, message string, args ...interface{}) *Error {
return ErrorLocf(
pos.Src.Name,
pos.Line,
pos.Column,
message,
args...,
)
}
func ErrorLocf(file string, line int, col int, message string, args ...interface{}) *Error {
var extensions map[string]interface{}
if file != "" {
extensions = map[string]interface{}{"file": file}
}
return &Error{
Message: fmt.Sprintf(message, args...),
Extensions: extensions,
Locations: []Location{
{Line: line, Column: col},
},
}
}
@@ -1,58 +0,0 @@
package lexer
import (
"math"
"strings"
)
// blockStringValue produces the value of a block string from its parsed raw value, similar to
// Coffeescript's block string, Python's docstring trim or Ruby's strip_heredoc.
//
// This implements the GraphQL spec's BlockStringValue() static algorithm.
func blockStringValue(raw string) string {
lines := strings.Split(raw, "\n")
commonIndent := math.MaxInt32
for _, line := range lines {
indent := leadingWhitespace(line)
if indent < len(line) && indent < commonIndent {
commonIndent = indent
if commonIndent == 0 {
break
}
}
}
if commonIndent != math.MaxInt32 && len(lines) > 0 {
for i := 1; i < len(lines); i++ {
if len(lines[i]) < commonIndent {
lines[i] = ""
} else {
lines[i] = lines[i][commonIndent:]
}
}
}
start := 0
end := len(lines)
for start < end && leadingWhitespace(lines[start]) == math.MaxInt32 {
start++
}
for start < end && leadingWhitespace(lines[end-1]) == math.MaxInt32 {
end--
}
return strings.Join(lines[start:end], "\n")
}
func leadingWhitespace(str string) int {
for i, r := range str {
if r != ' ' && r != '\t' {
return i
}
}
// this line is made up entirely of whitespace, its leading whitespace doesnt count.
return math.MaxInt32
}
@@ -1,517 +0,0 @@
package lexer
import (
"bytes"
"unicode/utf8"
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
"github.com/open-policy-agent/opa/internal/gqlparser/gqlerror"
)
// Lexer turns graphql request and schema strings into tokens
type Lexer struct {
*ast.Source
// An offset into the string in bytes
start int
// An offset into the string in runes
startRunes int
// An offset into the string in bytes
end int
// An offset into the string in runes
endRunes int
// the current line number
line int
// An offset into the string in rune
lineStartRunes int
}
func New(src *ast.Source) Lexer {
return Lexer{
Source: src,
line: 1,
}
}
// take one rune from input and advance end
func (s *Lexer) peek() (rune, int) {
return utf8.DecodeRuneInString(s.Input[s.end:])
}
func (s *Lexer) makeToken(kind Type) (Token, error) {
return s.makeValueToken(kind, s.Input[s.start:s.end])
}
func (s *Lexer) makeValueToken(kind Type, value string) (Token, error) {
return Token{
Kind: kind,
Value: value,
Pos: ast.Position{
Start: s.startRunes,
End: s.endRunes,
Line: s.line,
Column: s.startRunes - s.lineStartRunes + 1,
Src: s.Source,
},
}, nil
}
func (s *Lexer) makeError(format string, args ...interface{}) (Token, error) {
column := s.endRunes - s.lineStartRunes + 1
return Token{
Kind: Invalid,
Pos: ast.Position{
Start: s.startRunes,
End: s.endRunes,
Line: s.line,
Column: column,
Src: s.Source,
},
}, gqlerror.ErrorLocf(s.Source.Name, s.line, column, format, args...)
}
// ReadToken gets the next token from the source starting at the given position.
//
// This skips over whitespace and comments until it finds the next lexable
// token, then lexes punctuators immediately or calls the appropriate helper
// function for more complicated tokens.
func (s *Lexer) ReadToken() (token Token, err error) {
s.ws()
s.start = s.end
s.startRunes = s.endRunes
if s.end >= len(s.Input) {
return s.makeToken(EOF)
}
r := s.Input[s.start]
s.end++
s.endRunes++
switch r {
case '!':
return s.makeValueToken(Bang, "")
case '$':
return s.makeValueToken(Dollar, "")
case '&':
return s.makeValueToken(Amp, "")
case '(':
return s.makeValueToken(ParenL, "")
case ')':
return s.makeValueToken(ParenR, "")
case '.':
if len(s.Input) > s.start+2 && s.Input[s.start:s.start+3] == "..." {
s.end += 2
s.endRunes += 2
return s.makeValueToken(Spread, "")
}
case ':':
return s.makeValueToken(Colon, "")
case '=':
return s.makeValueToken(Equals, "")
case '@':
return s.makeValueToken(At, "")
case '[':
return s.makeValueToken(BracketL, "")
case ']':
return s.makeValueToken(BracketR, "")
case '{':
return s.makeValueToken(BraceL, "")
case '}':
return s.makeValueToken(BraceR, "")
case '|':
return s.makeValueToken(Pipe, "")
case '#':
if comment, err := s.readComment(); err != nil {
return comment, err
}
return s.ReadToken()
case '_', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z':
return s.readName()
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
return s.readNumber()
case '"':
if len(s.Input) > s.start+2 && s.Input[s.start:s.start+3] == `"""` {
return s.readBlockString()
}
return s.readString()
}
s.end--
s.endRunes--
if r < 0x0020 && r != 0x0009 && r != 0x000a && r != 0x000d {
return s.makeError(`Cannot contain the invalid character "\u%04d"`, r)
}
if r == '\'' {
return s.makeError(`Unexpected single quote character ('), did you mean to use a double quote (")?`)
}
return s.makeError(`Cannot parse the unexpected character "%s".`, string(r))
}
// ws reads from body starting at startPosition until it finds a non-whitespace
// or commented character, and updates the token end to include all whitespace
func (s *Lexer) ws() {
for s.end < len(s.Input) {
switch s.Input[s.end] {
case '\t', ' ', ',':
s.end++
s.endRunes++
case '\n':
s.end++
s.endRunes++
s.line++
s.lineStartRunes = s.endRunes
case '\r':
s.end++
s.endRunes++
s.line++
s.lineStartRunes = s.endRunes
// skip the following newline if its there
if s.end < len(s.Input) && s.Input[s.end] == '\n' {
s.end++
s.endRunes++
}
// byte order mark, given ws is hot path we aren't relying on the unicode package here.
case 0xef:
if s.end+2 < len(s.Input) && s.Input[s.end+1] == 0xBB && s.Input[s.end+2] == 0xBF {
s.end += 3
s.endRunes++
} else {
return
}
default:
return
}
}
}
// readComment from the input
//
// #[\u0009\u0020-\uFFFF]*
func (s *Lexer) readComment() (Token, error) {
for s.end < len(s.Input) {
r, w := s.peek()
// SourceCharacter but not LineTerminator
if r > 0x001f || r == '\t' {
s.end += w
s.endRunes++
} else {
break
}
}
return s.makeToken(Comment)
}
// readNumber from the input, either a float
// or an int depending on whether a decimal point appears.
//
// Int: -?(0|[1-9][0-9]*)
// Float: -?(0|[1-9][0-9]*)(\.[0-9]+)?((E|e)(+|-)?[0-9]+)?
func (s *Lexer) readNumber() (Token, error) {
float := false
// backup to the first digit
s.end--
s.endRunes--
s.acceptByte('-')
if s.acceptByte('0') {
if consumed := s.acceptDigits(); consumed != 0 {
s.end -= consumed
s.endRunes -= consumed
return s.makeError("Invalid number, unexpected digit after 0: %s.", s.describeNext())
}
} else {
if consumed := s.acceptDigits(); consumed == 0 {
return s.makeError("Invalid number, expected digit but got: %s.", s.describeNext())
}
}
if s.acceptByte('.') {
float = true
if consumed := s.acceptDigits(); consumed == 0 {
return s.makeError("Invalid number, expected digit but got: %s.", s.describeNext())
}
}
if s.acceptByte('e', 'E') {
float = true
s.acceptByte('-', '+')
if consumed := s.acceptDigits(); consumed == 0 {
return s.makeError("Invalid number, expected digit but got: %s.", s.describeNext())
}
}
if float {
return s.makeToken(Float)
}
return s.makeToken(Int)
}
// acceptByte if it matches any of given bytes, returning true if it found anything
func (s *Lexer) acceptByte(bytes ...uint8) bool {
if s.end >= len(s.Input) {
return false
}
for _, accepted := range bytes {
if s.Input[s.end] == accepted {
s.end++
s.endRunes++
return true
}
}
return false
}
// acceptDigits from the input, returning the number of digits it found
func (s *Lexer) acceptDigits() int {
consumed := 0
for s.end < len(s.Input) && s.Input[s.end] >= '0' && s.Input[s.end] <= '9' {
s.end++
s.endRunes++
consumed++
}
return consumed
}
// describeNext peeks at the input and returns a human readable string. This should will alloc
// and should only be used in errors
func (s *Lexer) describeNext() string {
if s.end < len(s.Input) {
return `"` + string(s.Input[s.end]) + `"`
}
return "<EOF>"
}
// readString from the input
//
// "([^"\\\u000A\u000D]|(\\(u[0-9a-fA-F]{4}|["\\/bfnrt])))*"
func (s *Lexer) readString() (Token, error) {
inputLen := len(s.Input)
// this buffer is lazily created only if there are escape characters.
var buf *bytes.Buffer
// skip the opening quote
s.start++
s.startRunes++
for s.end < inputLen {
r := s.Input[s.end]
if r == '\n' || r == '\r' {
break
}
if r < 0x0020 && r != '\t' {
return s.makeError(`Invalid character within String: "\u%04d".`, r)
}
switch r {
default:
var char = rune(r)
var w = 1
// skip unicode overhead if we are in the ascii range
if r >= 127 {
char, w = utf8.DecodeRuneInString(s.Input[s.end:])
}
s.end += w
s.endRunes++
if buf != nil {
buf.WriteRune(char)
}
case '"':
t, err := s.makeToken(String)
// the token should not include the quotes in its value, but should cover them in its position
t.Pos.Start--
t.Pos.End++
if buf != nil {
t.Value = buf.String()
}
// skip the close quote
s.end++
s.endRunes++
return t, err
case '\\':
if s.end+1 >= inputLen {
s.end++
s.endRunes++
return s.makeError(`Invalid character escape sequence.`)
}
if buf == nil {
buf = bytes.NewBufferString(s.Input[s.start:s.end])
}
escape := s.Input[s.end+1]
if escape == 'u' {
if s.end+6 >= inputLen {
s.end++
s.endRunes++
return s.makeError("Invalid character escape sequence: \\%s.", s.Input[s.end:])
}
r, ok := unhex(s.Input[s.end+2 : s.end+6])
if !ok {
s.end++
s.endRunes++
return s.makeError("Invalid character escape sequence: \\%s.", s.Input[s.end:s.end+5])
}
buf.WriteRune(r)
s.end += 6
s.endRunes += 6
} else {
switch escape {
case '"', '/', '\\':
buf.WriteByte(escape)
case 'b':
buf.WriteByte('\b')
case 'f':
buf.WriteByte('\f')
case 'n':
buf.WriteByte('\n')
case 'r':
buf.WriteByte('\r')
case 't':
buf.WriteByte('\t')
default:
s.end++
s.endRunes++
return s.makeError("Invalid character escape sequence: \\%s.", string(escape))
}
s.end += 2
s.endRunes += 2
}
}
}
return s.makeError("Unterminated string.")
}
// readBlockString from the input
//
// """("?"?(\\"""|\\(?!=""")|[^"\\]))*"""
func (s *Lexer) readBlockString() (Token, error) {
inputLen := len(s.Input)
var buf bytes.Buffer
// skip the opening quote
s.start += 3
s.startRunes += 3
s.end += 2
s.endRunes += 2
for s.end < inputLen {
r := s.Input[s.end]
// Closing triple quote (""")
if r == '"' && s.end+3 <= inputLen && s.Input[s.end:s.end+3] == `"""` {
t, err := s.makeValueToken(BlockString, blockStringValue(buf.String()))
// the token should not include the quotes in its value, but should cover them in its position
t.Pos.Start -= 3
t.Pos.End += 3
// skip the close quote
s.end += 3
s.endRunes += 3
return t, err
}
// SourceCharacter
if r < 0x0020 && r != '\t' && r != '\n' && r != '\r' {
return s.makeError(`Invalid character within String: "\u%04d".`, r)
}
if r == '\\' && s.end+4 <= inputLen && s.Input[s.end:s.end+4] == `\"""` {
buf.WriteString(`"""`)
s.end += 4
s.endRunes += 4
} else if r == '\r' {
if s.end+1 < inputLen && s.Input[s.end+1] == '\n' {
s.end++
s.endRunes++
}
buf.WriteByte('\n')
s.end++
s.endRunes++
s.line++
s.lineStartRunes = s.endRunes
} else {
var char = rune(r)
var w = 1
// skip unicode overhead if we are in the ascii range
if r >= 127 {
char, w = utf8.DecodeRuneInString(s.Input[s.end:])
}
s.end += w
s.endRunes++
buf.WriteRune(char)
if r == '\n' {
s.line++
s.lineStartRunes = s.endRunes
}
}
}
return s.makeError("Unterminated string.")
}
func unhex(b string) (v rune, ok bool) {
for _, c := range b {
v <<= 4
switch {
case '0' <= c && c <= '9':
v |= c - '0'
case 'a' <= c && c <= 'f':
v |= c - 'a' + 10
case 'A' <= c && c <= 'F':
v |= c - 'A' + 10
default:
return 0, false
}
}
return v, true
}
// readName from the input
//
// [_A-Za-z][_0-9A-Za-z]*
func (s *Lexer) readName() (Token, error) {
for s.end < len(s.Input) {
r, w := s.peek()
if (r >= '0' && r <= '9') || (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || r == '_' {
s.end += w
s.endRunes++
} else {
break
}
}
return s.makeToken(Name)
}
@@ -1,692 +0,0 @@
encoding:
- name: disallows uncommon control characters
input: "\u0007"
error:
message: 'Cannot contain the invalid character "\u0007"'
locations: [{line: 1, column: 1}]
- name: accepts BOM header
input: "\uFEFF foo"
tokens:
-
kind: NAME
start: 2
end: 5
value: 'foo'
simple tokens:
- name: records line and column
input: "\n \r\n \r foo\n"
tokens:
-
kind: NAME
start: 8
end: 11
line: 4
column: 3
value: 'foo'
- name: skips whitespace
input: "\n\n foo\n\n\n"
tokens:
-
kind: NAME
start: 6
end: 9
value: 'foo'
- name: skips comments
input: "\n #comment\n foo#comment\n"
tokens:
-
kind: NAME
start: 18
end: 21
value: 'foo'
- name: skips commas
input: ",,,foo,,,"
tokens:
-
kind: NAME
start: 3
end: 6
value: 'foo'
- name: errors respect whitespace
input: "\n\n ?\n\n\n"
error:
message: 'Cannot parse the unexpected character "?".'
locations: [{line: 3, column: 5}]
string: |
Syntax Error: Cannot parse the unexpected character "?".
GraphQL request (3:5)
2:
3: ?
^
4:
- name: lex reports useful information for dashes in names
input: "a-b"
error:
message: 'Invalid number, expected digit but got: "b".'
locations: [{ line: 1, column: 3 }]
tokens:
-
kind: Name
start: 0
end: 1
value: a
lexes strings:
- name: basic
input: '"simple"'
tokens:
-
kind: STRING
start: 0
end: 8
value: 'simple'
- name: whitespace
input: '" white space "'
tokens:
-
kind: STRING
start: 0
end: 15
value: ' white space '
- name: quote
input: '"quote \""'
tokens:
-
kind: STRING
start: 0
end: 10
value: 'quote "'
- name: escaped
input: '"escaped \n\r\b\t\f"'
tokens:
-
kind: STRING
start: 0
end: 20
value: "escaped \n\r\b\t\f"
- name: slashes
input: '"slashes \\ \/"'
tokens:
-
kind: STRING
start: 0
end: 15
value: 'slashes \ /'
- name: unicode
input: '"unicode \u1234\u5678\u90AB\uCDEF"'
tokens:
-
kind: STRING
start: 0
end: 34
value: "unicode \u1234\u5678\u90AB\uCDEF"
lex reports useful string errors:
- name: unterminated
input: '"'
error:
message: "Unterminated string."
locations: [{ line: 1, column: 2 }]
- name: no end quote
input: '"no end quote'
error:
message: 'Unterminated string.'
locations: [{ line: 1, column: 14 }]
- name: single quotes
input: "'single quotes'"
error:
message: "Unexpected single quote character ('), did you mean to use a double quote (\")?"
locations: [{ line: 1, column: 1 }]
- name: control characters
input: "\"contains unescaped \u0007 control char\""
error:
message: 'Invalid character within String: "\u0007".'
locations: [{ line: 1, column: 21 }]
- name: null byte
input: "\"null-byte is not \u0000 end of file\""
error:
message: 'Invalid character within String: "\u0000".'
locations: [{ line: 1, column: 19 }]
- name: unterminated newline
input: "\"multi\nline\""
error:
message: 'Unterminated string.'
locations: [{line: 1, column: 7 }]
- name: unterminated carriage return
input: "\"multi\rline\""
error:
message: 'Unterminated string.'
locations: [{ line: 1, column: 7 }]
- name: bad escape character
input: '"bad \z esc"'
error:
message: 'Invalid character escape sequence: \z.'
locations: [{ line: 1, column: 7 }]
- name: hex escape sequence
input: '"bad \x esc"'
error:
message: 'Invalid character escape sequence: \x.'
locations: [{ line: 1, column: 7 }]
- name: short escape sequence
input: '"bad \u1 esc"'
error:
message: 'Invalid character escape sequence: \u1 es.'
locations: [{ line: 1, column: 7 }]
- name: invalid escape sequence 1
input: '"bad \u0XX1 esc"'
error:
message: 'Invalid character escape sequence: \u0XX1.'
locations: [{ line: 1, column: 7 }]
- name: invalid escape sequence 2
input: '"bad \uXXXX esc"'
error:
message: 'Invalid character escape sequence: \uXXXX.'
locations: [{ line: 1, column: 7 }]
- name: invalid escape sequence 3
input: '"bad \uFXXX esc"'
error:
message: 'Invalid character escape sequence: \uFXXX.'
locations: [{ line: 1, column: 7 }]
- name: invalid character escape sequence
input: '"bad \uXXXF esc"'
error:
message: 'Invalid character escape sequence: \uXXXF.'
locations: [{ line: 1, column: 7 }]
lexes block strings:
- name: simple
input: '"""simple"""'
tokens:
-
kind: BLOCK_STRING
start: 0
end: 12
value: 'simple'
- name: white space
input: '""" white space """'
tokens:
-
kind: BLOCK_STRING
start: 0
end: 19
value: ' white space '
- name: contains quote
input: '"""contains " quote"""'
tokens:
-
kind: BLOCK_STRING
start: 0
end: 22
value: 'contains " quote'
- name: contains triplequote
input: "\"\"\"contains \\\"\"\" triplequote\"\"\""
tokens:
-
kind: BLOCK_STRING
start: 0
end: 31
value: 'contains """ triplequote'
- name: multi line
input: "\"\"\"multi\nline\"\"\""
tokens:
-
kind: BLOCK_STRING
start: 0
end: 16
value: "multi\nline"
- name: multi line normalized
input: "\"\"\"multi\rline\r\nnormalized\"\"\""
tokens:
-
kind: BLOCK_STRING
start: 0
end: 28
value: "multi\nline\nnormalized"
- name: unescaped
input: '"""unescaped \n\r\b\t\f\u1234"""'
tokens:
-
kind: BLOCK_STRING
start: 0
end: 32
value: 'unescaped \n\r\b\t\f\u1234'
- name: slashes
input: '"""slashes \\ \/"""'
tokens:
-
kind: BLOCK_STRING
start: 0
end: 19
value: 'slashes \\ \/'
- name: multiple lines
input: |
"""
spans
multiple
lines
"""
tokens:
-
kind: BLOCK_STRING
start: 0
end: 36
value: "spans\n multiple\n lines"
- name: records correct line and column after block string
input: |
"""
some
description
""" foo
tokens:
-
kind: BLOCK_STRING
value: "some\ndescription"
-
kind: NAME
start: 27
end: 30
line: 6
column: 5
value: 'foo'
lex reports useful block string errors:
- name: unterminated string
input: '"""'
error:
message: "Unterminated string."
locations: [{ line: 1, column: 4 }]
- name: unescaped control characters
input: "\"\"\"contains unescaped \u0007 control char\"\"\""
error:
message: 'Invalid character within String: "\u0007".'
locations: [{ line: 1, column: 23 }]
- name: null byte
input: "\"\"\"null-byte is not \u0000 end of file\"\"\""
error:
message: 'Invalid character within String: "\u0000".'
locations: [{ line: 1, column: 21 }]
lexes numbers:
- name: integer
input: "4"
tokens:
-
kind: INT
start: 0
end: 1
value: '4'
- name: float
input: "4.123"
tokens:
-
kind: FLOAT
start: 0
end: 5
value: '4.123'
- name: negative
input: "-4"
tokens:
-
kind: INT
start: 0
end: 2
value: '-4'
- name: nine
input: "9"
tokens:
-
kind: INT
start: 0
end: 1
value: '9'
- name: zero
input: "0"
tokens:
-
kind: INT
start: 0
end: 1
value: '0'
- name: negative float
input: "-4.123"
tokens:
-
kind: FLOAT
start: 0
end: 6
value: '-4.123'
- name: float leading zero
input: "0.123"
tokens:
-
kind: FLOAT
start: 0
end: 5
value: '0.123'
- name: exponent whole
input: "123e4"
tokens:
-
kind: FLOAT
start: 0
end: 5
value: '123e4'
- name: exponent uppercase
input: "123E4"
tokens:
-
kind: FLOAT
start: 0
end: 5
value: '123E4'
- name: exponent negative power
input: "123e-4"
tokens:
-
kind: FLOAT
start: 0
end: 6
value: '123e-4'
- name: exponent positive power
input: "123e+4"
tokens:
-
kind: FLOAT
start: 0
end: 6
value: '123e+4'
- name: exponent negative base
input: "-1.123e4"
tokens:
-
kind: FLOAT
start: 0
end: 8
value: '-1.123e4'
- name: exponent negative base upper
input: "-1.123E4"
tokens:
-
kind: FLOAT
start: 0
end: 8
value: '-1.123E4'
- name: exponent negative base negative power
input: "-1.123e-4"
tokens:
-
kind: FLOAT
start: 0
end: 9
value: '-1.123e-4'
- name: exponent negative base positive power
input: "-1.123e+4"
tokens:
-
kind: FLOAT
start: 0
end: 9
value: '-1.123e+4'
- name: exponent negative base large power
input: "-1.123e4567"
tokens:
-
kind: FLOAT
start: 0
end: 11
value: '-1.123e4567'
lex reports useful number errors:
- name: zero
input: "00"
error:
message: 'Invalid number, unexpected digit after 0: "0".'
locations: [{ line: 1, column: 2 }]
- name: positive
input: "+1"
error:
message: 'Cannot parse the unexpected character "+".'
locations: [{ line: 1, column: 1 }]
- name: trailing dot
input: "1."
error:
message: 'Invalid number, expected digit but got: <EOF>.'
locations: [{ line: 1, column: 3 }]
- name: traililng dot exponent
input: "1.e1"
error:
message: 'Invalid number, expected digit but got: "e".'
locations: [{ line: 1, column: 3 }]
- name: missing leading zero
input: ".123"
error:
message: 'Cannot parse the unexpected character ".".'
locations: [{ line: 1, column: 1 }]
- name: characters
input: "1.A"
error:
message: 'Invalid number, expected digit but got: "A".'
locations: [{ line: 1, column: 3 }]
- name: negative characters
input: "-A"
error:
message: 'Invalid number, expected digit but got: "A".'
locations: [{ line: 1, column: 2 }]
- name: missing exponent
input: '1.0e'
error:
message: 'Invalid number, expected digit but got: <EOF>.'
locations: [{ line: 1, column: 5 }]
- name: character exponent
input: "1.0eA"
error:
message: 'Invalid number, expected digit but got: "A".'
locations: [{ line: 1, column: 5 }]
lexes punctuation:
- name: bang
input: "!"
tokens:
-
kind: BANG
start: 0
end: 1
value: undefined
- name: dollar
input: "$"
tokens:
-
kind: DOLLAR
start: 0
end: 1
value: undefined
- name: open paren
input: "("
tokens:
-
kind: PAREN_L
start: 0
end: 1
value: undefined
- name: close paren
input: ")"
tokens:
-
kind: PAREN_R
start: 0
end: 1
value: undefined
- name: spread
input: "..."
tokens:
-
kind: SPREAD
start: 0
end: 3
value: undefined
- name: colon
input: ":"
tokens:
-
kind: COLON
start: 0
end: 1
value: undefined
- name: equals
input: "="
tokens:
-
kind: EQUALS
start: 0
end: 1
value: undefined
- name: at
input: "@"
tokens:
-
kind: AT
start: 0
end: 1
value: undefined
- name: open bracket
input: "["
tokens:
-
kind: BRACKET_L
start: 0
end: 1
value: undefined
- name: close bracket
input: "]"
tokens:
-
kind: BRACKET_R
start: 0
end: 1
value: undefined
- name: open brace
input: "{"
tokens:
-
kind: BRACE_L
start: 0
end: 1
value: undefined
- name: close brace
input: "}"
tokens:
-
kind: BRACE_R
start: 0
end: 1
value: undefined
- name: pipe
input: "|"
tokens:
-
kind: PIPE
start: 0
end: 1
value: undefined
lex reports useful unknown character error:
- name: not a spread
input: ".."
error:
message: 'Cannot parse the unexpected character ".".'
locations: [{ line: 1, column: 1 }]
- name: question mark
input: "?"
error:
message: 'Cannot parse the unexpected character "?".'
message: 'Cannot parse the unexpected character "?".'
locations: [{ line: 1, column: 1 }]
- name: unicode 203
input: "\u203B"
error:
message: 'Cannot parse the unexpected character "â".'
locations: [{ line: 1, column: 1 }]
- name: unicode 200
input: "\u200b"
error:
message: 'Cannot parse the unexpected character "â".'
locations: [{ line: 1, column: 1 }]
@@ -1,148 +0,0 @@
package lexer
import (
"strconv"
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
)
const (
Invalid Type = iota
EOF
Bang
Dollar
Amp
ParenL
ParenR
Spread
Colon
Equals
At
BracketL
BracketR
BraceL
BraceR
Pipe
Name
Int
Float
String
BlockString
Comment
)
func (t Type) Name() string {
switch t {
case Invalid:
return "Invalid"
case EOF:
return "EOF"
case Bang:
return "Bang"
case Dollar:
return "Dollar"
case Amp:
return "Amp"
case ParenL:
return "ParenL"
case ParenR:
return "ParenR"
case Spread:
return "Spread"
case Colon:
return "Colon"
case Equals:
return "Equals"
case At:
return "At"
case BracketL:
return "BracketL"
case BracketR:
return "BracketR"
case BraceL:
return "BraceL"
case BraceR:
return "BraceR"
case Pipe:
return "Pipe"
case Name:
return "Name"
case Int:
return "Int"
case Float:
return "Float"
case String:
return "String"
case BlockString:
return "BlockString"
case Comment:
return "Comment"
}
return "Unknown " + strconv.Itoa(int(t))
}
func (t Type) String() string {
switch t {
case Invalid:
return "<Invalid>"
case EOF:
return "<EOF>"
case Bang:
return "!"
case Dollar:
return "$"
case Amp:
return "&"
case ParenL:
return "("
case ParenR:
return ")"
case Spread:
return "..."
case Colon:
return ":"
case Equals:
return "="
case At:
return "@"
case BracketL:
return "["
case BracketR:
return "]"
case BraceL:
return "{"
case BraceR:
return "}"
case Pipe:
return "|"
case Name:
return "Name"
case Int:
return "Int"
case Float:
return "Float"
case String:
return "String"
case BlockString:
return "BlockString"
case Comment:
return "Comment"
}
return "Unknown " + strconv.Itoa(int(t))
}
// Kind represents a type of token. The types are predefined as constants.
type Type int
type Token struct {
Kind Type // The token type.
Value string // The literal value consumed.
Pos ast.Position // The file and line this token was read from
}
func (t Token) String() string {
if t.Value != "" {
return t.Kind.String() + " " + strconv.Quote(t.Value)
}
return t.Kind.String()
}
@@ -1,136 +0,0 @@
package parser
import (
"strconv"
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
"github.com/open-policy-agent/opa/internal/gqlparser/gqlerror"
"github.com/open-policy-agent/opa/internal/gqlparser/lexer"
)
type parser struct {
lexer lexer.Lexer
err error
peeked bool
peekToken lexer.Token
peekError error
prev lexer.Token
}
func (p *parser) peekPos() *ast.Position {
if p.err != nil {
return nil
}
peek := p.peek()
return &peek.Pos
}
func (p *parser) peek() lexer.Token {
if p.err != nil {
return p.prev
}
if !p.peeked {
p.peekToken, p.peekError = p.lexer.ReadToken()
p.peeked = true
}
return p.peekToken
}
func (p *parser) error(tok lexer.Token, format string, args ...interface{}) {
if p.err != nil {
return
}
p.err = gqlerror.ErrorLocf(tok.Pos.Src.Name, tok.Pos.Line, tok.Pos.Column, format, args...)
}
func (p *parser) next() lexer.Token {
if p.err != nil {
return p.prev
}
if p.peeked {
p.peeked = false
p.prev, p.err = p.peekToken, p.peekError
} else {
p.prev, p.err = p.lexer.ReadToken()
}
return p.prev
}
func (p *parser) expectKeyword(value string) lexer.Token {
tok := p.peek()
if tok.Kind == lexer.Name && tok.Value == value {
return p.next()
}
p.error(tok, "Expected %s, found %s", strconv.Quote(value), tok.String())
return tok
}
func (p *parser) expect(kind lexer.Type) lexer.Token {
tok := p.peek()
if tok.Kind == kind {
return p.next()
}
p.error(tok, "Expected %s, found %s", kind, tok.Kind.String())
return tok
}
func (p *parser) skip(kind lexer.Type) bool {
if p.err != nil {
return false
}
tok := p.peek()
if tok.Kind != kind {
return false
}
p.next()
return true
}
func (p *parser) unexpectedError() {
p.unexpectedToken(p.peek())
}
func (p *parser) unexpectedToken(tok lexer.Token) {
p.error(tok, "Unexpected %s", tok.String())
}
func (p *parser) many(start lexer.Type, end lexer.Type, cb func()) {
hasDef := p.skip(start)
if !hasDef {
return
}
for p.peek().Kind != end && p.err == nil {
cb()
}
p.next()
}
func (p *parser) some(start lexer.Type, end lexer.Type, cb func()) {
hasDef := p.skip(start)
if !hasDef {
return
}
called := false
for p.peek().Kind != end && p.err == nil {
called = true
cb()
}
if !called {
p.error(p.peek(), "expected at least one definition, found %s", p.peek().Kind.String())
return
}
p.next()
}
@@ -1,349 +0,0 @@
package parser
import (
"github.com/open-policy-agent/opa/internal/gqlparser/lexer"
//nolint:revive
. "github.com/open-policy-agent/opa/internal/gqlparser/ast"
)
func ParseQuery(source *Source) (*QueryDocument, error) {
p := parser{
lexer: lexer.New(source),
}
return p.parseQueryDocument(), p.err
}
func (p *parser) parseQueryDocument() *QueryDocument {
var doc QueryDocument
for p.peek().Kind != lexer.EOF {
if p.err != nil {
return &doc
}
doc.Position = p.peekPos()
switch p.peek().Kind {
case lexer.Name:
switch p.peek().Value {
case "query", "mutation", "subscription":
doc.Operations = append(doc.Operations, p.parseOperationDefinition())
case "fragment":
doc.Fragments = append(doc.Fragments, p.parseFragmentDefinition())
default:
p.unexpectedError()
}
case lexer.BraceL:
doc.Operations = append(doc.Operations, p.parseOperationDefinition())
default:
p.unexpectedError()
}
}
return &doc
}
func (p *parser) parseOperationDefinition() *OperationDefinition {
if p.peek().Kind == lexer.BraceL {
return &OperationDefinition{
Position: p.peekPos(),
Operation: Query,
SelectionSet: p.parseRequiredSelectionSet(),
}
}
var od OperationDefinition
od.Position = p.peekPos()
od.Operation = p.parseOperationType()
if p.peek().Kind == lexer.Name {
od.Name = p.next().Value
}
od.VariableDefinitions = p.parseVariableDefinitions()
od.Directives = p.parseDirectives(false)
od.SelectionSet = p.parseRequiredSelectionSet()
return &od
}
func (p *parser) parseOperationType() Operation {
tok := p.next()
switch tok.Value {
case "query":
return Query
case "mutation":
return Mutation
case "subscription":
return Subscription
}
p.unexpectedToken(tok)
return ""
}
func (p *parser) parseVariableDefinitions() VariableDefinitionList {
var defs []*VariableDefinition
p.many(lexer.ParenL, lexer.ParenR, func() {
defs = append(defs, p.parseVariableDefinition())
})
return defs
}
func (p *parser) parseVariableDefinition() *VariableDefinition {
var def VariableDefinition
def.Position = p.peekPos()
def.Variable = p.parseVariable()
p.expect(lexer.Colon)
def.Type = p.parseTypeReference()
if p.skip(lexer.Equals) {
def.DefaultValue = p.parseValueLiteral(true)
}
def.Directives = p.parseDirectives(false)
return &def
}
func (p *parser) parseVariable() string {
p.expect(lexer.Dollar)
return p.parseName()
}
func (p *parser) parseOptionalSelectionSet() SelectionSet {
var selections []Selection
p.some(lexer.BraceL, lexer.BraceR, func() {
selections = append(selections, p.parseSelection())
})
return SelectionSet(selections)
}
func (p *parser) parseRequiredSelectionSet() SelectionSet {
if p.peek().Kind != lexer.BraceL {
p.error(p.peek(), "Expected %s, found %s", lexer.BraceL, p.peek().Kind.String())
return nil
}
var selections []Selection
p.some(lexer.BraceL, lexer.BraceR, func() {
selections = append(selections, p.parseSelection())
})
return SelectionSet(selections)
}
func (p *parser) parseSelection() Selection {
if p.peek().Kind == lexer.Spread {
return p.parseFragment()
}
return p.parseField()
}
func (p *parser) parseField() *Field {
var field Field
field.Position = p.peekPos()
field.Alias = p.parseName()
if p.skip(lexer.Colon) {
field.Name = p.parseName()
} else {
field.Name = field.Alias
}
field.Arguments = p.parseArguments(false)
field.Directives = p.parseDirectives(false)
if p.peek().Kind == lexer.BraceL {
field.SelectionSet = p.parseOptionalSelectionSet()
}
return &field
}
func (p *parser) parseArguments(isConst bool) ArgumentList {
var arguments ArgumentList
p.many(lexer.ParenL, lexer.ParenR, func() {
arguments = append(arguments, p.parseArgument(isConst))
})
return arguments
}
func (p *parser) parseArgument(isConst bool) *Argument {
arg := Argument{}
arg.Position = p.peekPos()
arg.Name = p.parseName()
p.expect(lexer.Colon)
arg.Value = p.parseValueLiteral(isConst)
return &arg
}
func (p *parser) parseFragment() Selection {
p.expect(lexer.Spread)
if peek := p.peek(); peek.Kind == lexer.Name && peek.Value != "on" {
return &FragmentSpread{
Position: p.peekPos(),
Name: p.parseFragmentName(),
Directives: p.parseDirectives(false),
}
}
var def InlineFragment
def.Position = p.peekPos()
if p.peek().Value == "on" {
p.next() // "on"
def.TypeCondition = p.parseName()
}
def.Directives = p.parseDirectives(false)
def.SelectionSet = p.parseRequiredSelectionSet()
return &def
}
func (p *parser) parseFragmentDefinition() *FragmentDefinition {
var def FragmentDefinition
def.Position = p.peekPos()
p.expectKeyword("fragment")
def.Name = p.parseFragmentName()
def.VariableDefinition = p.parseVariableDefinitions()
p.expectKeyword("on")
def.TypeCondition = p.parseName()
def.Directives = p.parseDirectives(false)
def.SelectionSet = p.parseRequiredSelectionSet()
return &def
}
func (p *parser) parseFragmentName() string {
if p.peek().Value == "on" {
p.unexpectedError()
return ""
}
return p.parseName()
}
func (p *parser) parseValueLiteral(isConst bool) *Value {
token := p.peek()
var kind ValueKind
switch token.Kind {
case lexer.BracketL:
return p.parseList(isConst)
case lexer.BraceL:
return p.parseObject(isConst)
case lexer.Dollar:
if isConst {
p.unexpectedError()
return nil
}
return &Value{Position: &token.Pos, Raw: p.parseVariable(), Kind: Variable}
case lexer.Int:
kind = IntValue
case lexer.Float:
kind = FloatValue
case lexer.String:
kind = StringValue
case lexer.BlockString:
kind = BlockValue
case lexer.Name:
switch token.Value {
case "true", "false":
kind = BooleanValue
case "null":
kind = NullValue
default:
kind = EnumValue
}
default:
p.unexpectedError()
return nil
}
p.next()
return &Value{Position: &token.Pos, Raw: token.Value, Kind: kind}
}
func (p *parser) parseList(isConst bool) *Value {
var values ChildValueList
pos := p.peekPos()
p.many(lexer.BracketL, lexer.BracketR, func() {
values = append(values, &ChildValue{Value: p.parseValueLiteral(isConst)})
})
return &Value{Children: values, Kind: ListValue, Position: pos}
}
func (p *parser) parseObject(isConst bool) *Value {
var fields ChildValueList
pos := p.peekPos()
p.many(lexer.BraceL, lexer.BraceR, func() {
fields = append(fields, p.parseObjectField(isConst))
})
return &Value{Children: fields, Kind: ObjectValue, Position: pos}
}
func (p *parser) parseObjectField(isConst bool) *ChildValue {
field := ChildValue{}
field.Position = p.peekPos()
field.Name = p.parseName()
p.expect(lexer.Colon)
field.Value = p.parseValueLiteral(isConst)
return &field
}
func (p *parser) parseDirectives(isConst bool) []*Directive {
var directives []*Directive
for p.peek().Kind == lexer.At {
if p.err != nil {
break
}
directives = append(directives, p.parseDirective(isConst))
}
return directives
}
func (p *parser) parseDirective(isConst bool) *Directive {
p.expect(lexer.At)
return &Directive{
Position: p.peekPos(),
Name: p.parseName(),
Arguments: p.parseArguments(isConst),
}
}
func (p *parser) parseTypeReference() *Type {
var typ Type
if p.skip(lexer.BracketL) {
typ.Position = p.peekPos()
typ.Elem = p.parseTypeReference()
p.expect(lexer.BracketR)
} else {
typ.Position = p.peekPos()
typ.NamedType = p.parseName()
}
if p.skip(lexer.Bang) {
typ.NonNull = true
}
return &typ
}
func (p *parser) parseName() string {
token := p.expect(lexer.Name)
return token.Value
}
@@ -1,544 +0,0 @@
parser provides useful errors:
- name: unclosed paren
input: '{'
error:
message: "Expected Name, found <EOF>"
locations: [{line: 1, column: 2}]
- name: missing on in fragment
input: |
{ ...MissingOn }
fragment MissingOn Type
error:
message: 'Expected "on", found Name "Type"'
locations: [{ line: 2, column: 20 }]
- name: missing name after alias
input: '{ field: {} }'
error:
message: "Expected Name, found {"
locations: [{ line: 1, column: 10 }]
- name: not an operation
input: 'notanoperation Foo { field }'
error:
message: 'Unexpected Name "notanoperation"'
locations: [{ line: 1, column: 1 }]
- name: a wild splat appears
input: '...'
error:
message: 'Unexpected ...'
locations: [{ line: 1, column: 1}]
variables:
- name: are allowed in args
input: '{ field(complex: { a: { b: [ $var ] } }) }'
- name: are not allowed in default args
input: 'query Foo($x: Complex = { a: { b: [ $var ] } }) { field }'
error:
message: 'Unexpected $'
locations: [{ line: 1, column: 37 }]
- name: can have directives
input: 'query ($withDirective: String @first @second, $withoutDirective: String) { f }'
ast: |
<QueryDocument>
Operations: [OperationDefinition]
- <OperationDefinition>
Operation: Operation("query")
VariableDefinitions: [VariableDefinition]
- <VariableDefinition>
Variable: "withDirective"
Type: String
Directives: [Directive]
- <Directive>
Name: "first"
- <Directive>
Name: "second"
- <VariableDefinition>
Variable: "withoutDirective"
Type: String
SelectionSet: [Selection]
- <Field>
Alias: "f"
Name: "f"
fragments:
- name: can not be named 'on'
input: 'fragment on on on { on }'
error:
message: 'Unexpected Name "on"'
locations: [{ line: 1, column: 10 }]
- name: can not spread fragments called 'on'
input: '{ ...on }'
error:
message: 'Expected Name, found }'
locations: [{ line: 1, column: 9 }]
encoding:
- name: multibyte characters are supported
input: |
# This comment has a ਊ multi-byte character.
{ field(arg: "Has a ਊ multi-byte character.") }
ast: |
<QueryDocument>
Operations: [OperationDefinition]
- <OperationDefinition>
Operation: Operation("query")
SelectionSet: [Selection]
- <Field>
Alias: "field"
Name: "field"
Arguments: [Argument]
- <Argument>
Name: "arg"
Value: "Has a ਊ multi-byte character."
keywords are allowed anywhere a name is:
- name: on
input: |
query on {
... a
... on on { field }
}
fragment a on Type {
on(on: $on)
@on(on: on)
}
- name: subscription
input: |
query subscription {
... subscription
... on subscription { field }
}
fragment subscription on Type {
subscription(subscription: $subscription)
@subscription(subscription: subscription)
}
- name: true
input: |
query true {
... true
... on true { field }
}
fragment true on Type {
true(true: $true)
@true(true: true)
}
operations:
- name: anonymous mutation
input: 'mutation { mutationField }'
- name: named mutation
input: 'mutation Foo { mutationField }'
- name: anonymous subscription
input: 'subscription { subscriptionField }'
- name: named subscription
input: 'subscription Foo { subscriptionField }'
ast:
- name: simple query
input: |
{
node(id: 4) {
id,
name
}
}
ast: |
<QueryDocument>
Operations: [OperationDefinition]
- <OperationDefinition>
Operation: Operation("query")
SelectionSet: [Selection]
- <Field>
Alias: "node"
Name: "node"
Arguments: [Argument]
- <Argument>
Name: "id"
Value: 4
SelectionSet: [Selection]
- <Field>
Alias: "id"
Name: "id"
- <Field>
Alias: "name"
Name: "name"
- name: nameless query with no variables
input: |
query {
node {
id
}
}
ast: |
<QueryDocument>
Operations: [OperationDefinition]
- <OperationDefinition>
Operation: Operation("query")
SelectionSet: [Selection]
- <Field>
Alias: "node"
Name: "node"
SelectionSet: [Selection]
- <Field>
Alias: "id"
Name: "id"
- name: fragment defined variables
input: 'fragment a($v: Boolean = false) on t { f(v: $v) }'
ast: |
<QueryDocument>
Fragments: [FragmentDefinition]
- <FragmentDefinition>
Name: "a"
VariableDefinition: [VariableDefinition]
- <VariableDefinition>
Variable: "v"
Type: Boolean
DefaultValue: false
TypeCondition: "t"
SelectionSet: [Selection]
- <Field>
Alias: "f"
Name: "f"
Arguments: [Argument]
- <Argument>
Name: "v"
Value: $v
values:
- name: null
input: '{ f(id: null) }'
ast: |
<QueryDocument>
Operations: [OperationDefinition]
- <OperationDefinition>
Operation: Operation("query")
SelectionSet: [Selection]
- <Field>
Alias: "f"
Name: "f"
Arguments: [Argument]
- <Argument>
Name: "id"
Value: null
- name: strings
input: '{ f(long: """long""", short: "short") } '
ast: |
<QueryDocument>
Operations: [OperationDefinition]
- <OperationDefinition>
Operation: Operation("query")
SelectionSet: [Selection]
- <Field>
Alias: "f"
Name: "f"
Arguments: [Argument]
- <Argument>
Name: "long"
Value: "long"
- <Argument>
Name: "short"
Value: "short"
- name: list
input: '{ f(id: [1,2]) }'
ast: |
<QueryDocument>
Operations: [OperationDefinition]
- <OperationDefinition>
Operation: Operation("query")
SelectionSet: [Selection]
- <Field>
Alias: "f"
Name: "f"
Arguments: [Argument]
- <Argument>
Name: "id"
Value: [1,2]
types:
- name: common types
input: 'query ($string: String, $int: Int, $arr: [Arr], $notnull: [Arr!]!) { f }'
ast: |
<QueryDocument>
Operations: [OperationDefinition]
- <OperationDefinition>
Operation: Operation("query")
VariableDefinitions: [VariableDefinition]
- <VariableDefinition>
Variable: "string"
Type: String
- <VariableDefinition>
Variable: "int"
Type: Int
- <VariableDefinition>
Variable: "arr"
Type: [Arr]
- <VariableDefinition>
Variable: "notnull"
Type: [Arr!]!
SelectionSet: [Selection]
- <Field>
Alias: "f"
Name: "f"
large queries:
- name: kitchen sink
input: |
# Copyright (c) 2015-present, Facebook, Inc.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
query queryName($foo: ComplexType, $site: Site = MOBILE) {
whoever123is: node(id: [123, 456]) {
id ,
... on User @defer {
field2 {
id ,
alias: field1(first:10, after:$foo,) @include(if: $foo) {
id,
...frag
}
}
}
... @skip(unless: $foo) {
id
}
... {
id
}
}
}
mutation likeStory {
like(story: 123) @defer {
story {
id
}
}
}
subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) {
storyLikeSubscribe(input: $input) {
story {
likers {
count
}
likeSentence {
text
}
}
}
}
fragment frag on Friend {
foo(size: $size, bar: $b, obj: {key: "value", block: """
block string uses \"""
"""})
}
{
unnamed(truthy: true, falsey: false, nullish: null),
query
}
ast: |
<QueryDocument>
Operations: [OperationDefinition]
- <OperationDefinition>
Operation: Operation("query")
Name: "queryName"
VariableDefinitions: [VariableDefinition]
- <VariableDefinition>
Variable: "foo"
Type: ComplexType
- <VariableDefinition>
Variable: "site"
Type: Site
DefaultValue: MOBILE
SelectionSet: [Selection]
- <Field>
Alias: "whoever123is"
Name: "node"
Arguments: [Argument]
- <Argument>
Name: "id"
Value: [123,456]
SelectionSet: [Selection]
- <Field>
Alias: "id"
Name: "id"
- <InlineFragment>
TypeCondition: "User"
Directives: [Directive]
- <Directive>
Name: "defer"
SelectionSet: [Selection]
- <Field>
Alias: "field2"
Name: "field2"
SelectionSet: [Selection]
- <Field>
Alias: "id"
Name: "id"
- <Field>
Alias: "alias"
Name: "field1"
Arguments: [Argument]
- <Argument>
Name: "first"
Value: 10
- <Argument>
Name: "after"
Value: $foo
Directives: [Directive]
- <Directive>
Name: "include"
Arguments: [Argument]
- <Argument>
Name: "if"
Value: $foo
SelectionSet: [Selection]
- <Field>
Alias: "id"
Name: "id"
- <FragmentSpread>
Name: "frag"
- <InlineFragment>
Directives: [Directive]
- <Directive>
Name: "skip"
Arguments: [Argument]
- <Argument>
Name: "unless"
Value: $foo
SelectionSet: [Selection]
- <Field>
Alias: "id"
Name: "id"
- <InlineFragment>
SelectionSet: [Selection]
- <Field>
Alias: "id"
Name: "id"
- <OperationDefinition>
Operation: Operation("mutation")
Name: "likeStory"
SelectionSet: [Selection]
- <Field>
Alias: "like"
Name: "like"
Arguments: [Argument]
- <Argument>
Name: "story"
Value: 123
Directives: [Directive]
- <Directive>
Name: "defer"
SelectionSet: [Selection]
- <Field>
Alias: "story"
Name: "story"
SelectionSet: [Selection]
- <Field>
Alias: "id"
Name: "id"
- <OperationDefinition>
Operation: Operation("subscription")
Name: "StoryLikeSubscription"
VariableDefinitions: [VariableDefinition]
- <VariableDefinition>
Variable: "input"
Type: StoryLikeSubscribeInput
SelectionSet: [Selection]
- <Field>
Alias: "storyLikeSubscribe"
Name: "storyLikeSubscribe"
Arguments: [Argument]
- <Argument>
Name: "input"
Value: $input
SelectionSet: [Selection]
- <Field>
Alias: "story"
Name: "story"
SelectionSet: [Selection]
- <Field>
Alias: "likers"
Name: "likers"
SelectionSet: [Selection]
- <Field>
Alias: "count"
Name: "count"
- <Field>
Alias: "likeSentence"
Name: "likeSentence"
SelectionSet: [Selection]
- <Field>
Alias: "text"
Name: "text"
- <OperationDefinition>
Operation: Operation("query")
SelectionSet: [Selection]
- <Field>
Alias: "unnamed"
Name: "unnamed"
Arguments: [Argument]
- <Argument>
Name: "truthy"
Value: true
- <Argument>
Name: "falsey"
Value: false
- <Argument>
Name: "nullish"
Value: null
- <Field>
Alias: "query"
Name: "query"
Fragments: [FragmentDefinition]
- <FragmentDefinition>
Name: "frag"
TypeCondition: "Friend"
SelectionSet: [Selection]
- <Field>
Alias: "foo"
Name: "foo"
Arguments: [Argument]
- <Argument>
Name: "size"
Value: $size
- <Argument>
Name: "bar"
Value: $b
- <Argument>
Name: "obj"
Value: {key:"value",block:"block string uses \"\"\""}
fuzzer:
- name: 01
input: '{__typename{...}}'
error:
message: 'Expected {, found }'
locations: [{ line: 1, column: 16 }]
- name: 02
input: '{...{__typename{...{}}}}'
error:
message: 'expected at least one definition, found }'
locations: [{ line: 1, column: 21 }]
@@ -1,535 +0,0 @@
package parser
import (
//nolint:revive
. "github.com/open-policy-agent/opa/internal/gqlparser/ast"
"github.com/open-policy-agent/opa/internal/gqlparser/lexer"
)
func ParseSchema(source *Source) (*SchemaDocument, error) {
p := parser{
lexer: lexer.New(source),
}
ast, err := p.parseSchemaDocument(), p.err
if err != nil {
return nil, err
}
for _, def := range ast.Definitions {
def.BuiltIn = source.BuiltIn
}
for _, def := range ast.Extensions {
def.BuiltIn = source.BuiltIn
}
return ast, nil
}
func ParseSchemas(inputs ...*Source) (*SchemaDocument, error) {
ast := &SchemaDocument{}
for _, input := range inputs {
inputAst, err := ParseSchema(input)
if err != nil {
return nil, err
}
ast.Merge(inputAst)
}
return ast, nil
}
func (p *parser) parseSchemaDocument() *SchemaDocument {
var doc SchemaDocument
doc.Position = p.peekPos()
for p.peek().Kind != lexer.EOF {
if p.err != nil {
return nil
}
var description string
if p.peek().Kind == lexer.BlockString || p.peek().Kind == lexer.String {
description = p.parseDescription()
}
if p.peek().Kind != lexer.Name {
p.unexpectedError()
break
}
switch p.peek().Value {
case "scalar", "type", "interface", "union", "enum", "input":
doc.Definitions = append(doc.Definitions, p.parseTypeSystemDefinition(description))
case "schema":
doc.Schema = append(doc.Schema, p.parseSchemaDefinition(description))
case "directive":
doc.Directives = append(doc.Directives, p.parseDirectiveDefinition(description))
case "extend":
if description != "" {
p.unexpectedToken(p.prev)
}
p.parseTypeSystemExtension(&doc)
default:
p.unexpectedError()
return nil
}
}
return &doc
}
func (p *parser) parseDescription() string {
token := p.peek()
if token.Kind != lexer.BlockString && token.Kind != lexer.String {
return ""
}
return p.next().Value
}
func (p *parser) parseTypeSystemDefinition(description string) *Definition {
tok := p.peek()
if tok.Kind != lexer.Name {
p.unexpectedError()
return nil
}
switch tok.Value {
case "scalar":
return p.parseScalarTypeDefinition(description)
case "type":
return p.parseObjectTypeDefinition(description)
case "interface":
return p.parseInterfaceTypeDefinition(description)
case "union":
return p.parseUnionTypeDefinition(description)
case "enum":
return p.parseEnumTypeDefinition(description)
case "input":
return p.parseInputObjectTypeDefinition(description)
default:
p.unexpectedError()
return nil
}
}
func (p *parser) parseSchemaDefinition(description string) *SchemaDefinition {
p.expectKeyword("schema")
def := SchemaDefinition{Description: description}
def.Position = p.peekPos()
def.Description = description
def.Directives = p.parseDirectives(true)
p.some(lexer.BraceL, lexer.BraceR, func() {
def.OperationTypes = append(def.OperationTypes, p.parseOperationTypeDefinition())
})
return &def
}
func (p *parser) parseOperationTypeDefinition() *OperationTypeDefinition {
var op OperationTypeDefinition
op.Position = p.peekPos()
op.Operation = p.parseOperationType()
p.expect(lexer.Colon)
op.Type = p.parseName()
return &op
}
func (p *parser) parseScalarTypeDefinition(description string) *Definition {
p.expectKeyword("scalar")
var def Definition
def.Position = p.peekPos()
def.Kind = Scalar
def.Description = description
def.Name = p.parseName()
def.Directives = p.parseDirectives(true)
return &def
}
func (p *parser) parseObjectTypeDefinition(description string) *Definition {
p.expectKeyword("type")
var def Definition
def.Position = p.peekPos()
def.Kind = Object
def.Description = description
def.Name = p.parseName()
def.Interfaces = p.parseImplementsInterfaces()
def.Directives = p.parseDirectives(true)
def.Fields = p.parseFieldsDefinition()
return &def
}
func (p *parser) parseImplementsInterfaces() []string {
var types []string
if p.peek().Value == "implements" {
p.next()
// optional leading ampersand
p.skip(lexer.Amp)
types = append(types, p.parseName())
for p.skip(lexer.Amp) && p.err == nil {
types = append(types, p.parseName())
}
}
return types
}
func (p *parser) parseFieldsDefinition() FieldList {
var defs FieldList
p.some(lexer.BraceL, lexer.BraceR, func() {
defs = append(defs, p.parseFieldDefinition())
})
return defs
}
func (p *parser) parseFieldDefinition() *FieldDefinition {
var def FieldDefinition
def.Position = p.peekPos()
def.Description = p.parseDescription()
def.Name = p.parseName()
def.Arguments = p.parseArgumentDefs()
p.expect(lexer.Colon)
def.Type = p.parseTypeReference()
def.Directives = p.parseDirectives(true)
return &def
}
func (p *parser) parseArgumentDefs() ArgumentDefinitionList {
var args ArgumentDefinitionList
p.some(lexer.ParenL, lexer.ParenR, func() {
args = append(args, p.parseArgumentDef())
})
return args
}
func (p *parser) parseArgumentDef() *ArgumentDefinition {
var def ArgumentDefinition
def.Position = p.peekPos()
def.Description = p.parseDescription()
def.Name = p.parseName()
p.expect(lexer.Colon)
def.Type = p.parseTypeReference()
if p.skip(lexer.Equals) {
def.DefaultValue = p.parseValueLiteral(true)
}
def.Directives = p.parseDirectives(true)
return &def
}
func (p *parser) parseInputValueDef() *FieldDefinition {
var def FieldDefinition
def.Position = p.peekPos()
def.Description = p.parseDescription()
def.Name = p.parseName()
p.expect(lexer.Colon)
def.Type = p.parseTypeReference()
if p.skip(lexer.Equals) {
def.DefaultValue = p.parseValueLiteral(true)
}
def.Directives = p.parseDirectives(true)
return &def
}
func (p *parser) parseInterfaceTypeDefinition(description string) *Definition {
p.expectKeyword("interface")
var def Definition
def.Position = p.peekPos()
def.Kind = Interface
def.Description = description
def.Name = p.parseName()
def.Interfaces = p.parseImplementsInterfaces()
def.Directives = p.parseDirectives(true)
def.Fields = p.parseFieldsDefinition()
return &def
}
func (p *parser) parseUnionTypeDefinition(description string) *Definition {
p.expectKeyword("union")
var def Definition
def.Position = p.peekPos()
def.Kind = Union
def.Description = description
def.Name = p.parseName()
def.Directives = p.parseDirectives(true)
def.Types = p.parseUnionMemberTypes()
return &def
}
func (p *parser) parseUnionMemberTypes() []string {
var types []string
if p.skip(lexer.Equals) {
// optional leading pipe
p.skip(lexer.Pipe)
types = append(types, p.parseName())
for p.skip(lexer.Pipe) && p.err == nil {
types = append(types, p.parseName())
}
}
return types
}
func (p *parser) parseEnumTypeDefinition(description string) *Definition {
p.expectKeyword("enum")
var def Definition
def.Position = p.peekPos()
def.Kind = Enum
def.Description = description
def.Name = p.parseName()
def.Directives = p.parseDirectives(true)
def.EnumValues = p.parseEnumValuesDefinition()
return &def
}
func (p *parser) parseEnumValuesDefinition() EnumValueList {
var values EnumValueList
p.some(lexer.BraceL, lexer.BraceR, func() {
values = append(values, p.parseEnumValueDefinition())
})
return values
}
func (p *parser) parseEnumValueDefinition() *EnumValueDefinition {
return &EnumValueDefinition{
Position: p.peekPos(),
Description: p.parseDescription(),
Name: p.parseName(),
Directives: p.parseDirectives(true),
}
}
func (p *parser) parseInputObjectTypeDefinition(description string) *Definition {
p.expectKeyword("input")
var def Definition
def.Position = p.peekPos()
def.Kind = InputObject
def.Description = description
def.Name = p.parseName()
def.Directives = p.parseDirectives(true)
def.Fields = p.parseInputFieldsDefinition()
return &def
}
func (p *parser) parseInputFieldsDefinition() FieldList {
var values FieldList
p.some(lexer.BraceL, lexer.BraceR, func() {
values = append(values, p.parseInputValueDef())
})
return values
}
func (p *parser) parseTypeSystemExtension(doc *SchemaDocument) {
p.expectKeyword("extend")
switch p.peek().Value {
case "schema":
doc.SchemaExtension = append(doc.SchemaExtension, p.parseSchemaExtension())
case "scalar":
doc.Extensions = append(doc.Extensions, p.parseScalarTypeExtension())
case "type":
doc.Extensions = append(doc.Extensions, p.parseObjectTypeExtension())
case "interface":
doc.Extensions = append(doc.Extensions, p.parseInterfaceTypeExtension())
case "union":
doc.Extensions = append(doc.Extensions, p.parseUnionTypeExtension())
case "enum":
doc.Extensions = append(doc.Extensions, p.parseEnumTypeExtension())
case "input":
doc.Extensions = append(doc.Extensions, p.parseInputObjectTypeExtension())
default:
p.unexpectedError()
}
}
func (p *parser) parseSchemaExtension() *SchemaDefinition {
p.expectKeyword("schema")
var def SchemaDefinition
def.Position = p.peekPos()
def.Directives = p.parseDirectives(true)
p.some(lexer.BraceL, lexer.BraceR, func() {
def.OperationTypes = append(def.OperationTypes, p.parseOperationTypeDefinition())
})
if len(def.Directives) == 0 && len(def.OperationTypes) == 0 {
p.unexpectedError()
}
return &def
}
func (p *parser) parseScalarTypeExtension() *Definition {
p.expectKeyword("scalar")
var def Definition
def.Position = p.peekPos()
def.Kind = Scalar
def.Name = p.parseName()
def.Directives = p.parseDirectives(true)
if len(def.Directives) == 0 {
p.unexpectedError()
}
return &def
}
func (p *parser) parseObjectTypeExtension() *Definition {
p.expectKeyword("type")
var def Definition
def.Position = p.peekPos()
def.Kind = Object
def.Name = p.parseName()
def.Interfaces = p.parseImplementsInterfaces()
def.Directives = p.parseDirectives(true)
def.Fields = p.parseFieldsDefinition()
if len(def.Interfaces) == 0 && len(def.Directives) == 0 && len(def.Fields) == 0 {
p.unexpectedError()
}
return &def
}
func (p *parser) parseInterfaceTypeExtension() *Definition {
p.expectKeyword("interface")
var def Definition
def.Position = p.peekPos()
def.Kind = Interface
def.Name = p.parseName()
def.Directives = p.parseDirectives(true)
def.Fields = p.parseFieldsDefinition()
if len(def.Directives) == 0 && len(def.Fields) == 0 {
p.unexpectedError()
}
return &def
}
func (p *parser) parseUnionTypeExtension() *Definition {
p.expectKeyword("union")
var def Definition
def.Position = p.peekPos()
def.Kind = Union
def.Name = p.parseName()
def.Directives = p.parseDirectives(true)
def.Types = p.parseUnionMemberTypes()
if len(def.Directives) == 0 && len(def.Types) == 0 {
p.unexpectedError()
}
return &def
}
func (p *parser) parseEnumTypeExtension() *Definition {
p.expectKeyword("enum")
var def Definition
def.Position = p.peekPos()
def.Kind = Enum
def.Name = p.parseName()
def.Directives = p.parseDirectives(true)
def.EnumValues = p.parseEnumValuesDefinition()
if len(def.Directives) == 0 && len(def.EnumValues) == 0 {
p.unexpectedError()
}
return &def
}
func (p *parser) parseInputObjectTypeExtension() *Definition {
p.expectKeyword("input")
var def Definition
def.Position = p.peekPos()
def.Kind = InputObject
def.Name = p.parseName()
def.Directives = p.parseDirectives(false)
def.Fields = p.parseInputFieldsDefinition()
if len(def.Directives) == 0 && len(def.Fields) == 0 {
p.unexpectedError()
}
return &def
}
func (p *parser) parseDirectiveDefinition(description string) *DirectiveDefinition {
p.expectKeyword("directive")
p.expect(lexer.At)
var def DirectiveDefinition
def.Position = p.peekPos()
def.Description = description
def.Name = p.parseName()
def.Arguments = p.parseArgumentDefs()
if peek := p.peek(); peek.Kind == lexer.Name && peek.Value == "repeatable" {
def.IsRepeatable = true
p.skip(lexer.Name)
}
p.expectKeyword("on")
def.Locations = p.parseDirectiveLocations()
return &def
}
func (p *parser) parseDirectiveLocations() []DirectiveLocation {
p.skip(lexer.Pipe)
locations := []DirectiveLocation{p.parseDirectiveLocation()}
for p.skip(lexer.Pipe) && p.err == nil {
locations = append(locations, p.parseDirectiveLocation())
}
return locations
}
func (p *parser) parseDirectiveLocation() DirectiveLocation {
name := p.expect(lexer.Name)
switch name.Value {
case `QUERY`:
return LocationQuery
case `MUTATION`:
return LocationMutation
case `SUBSCRIPTION`:
return LocationSubscription
case `FIELD`:
return LocationField
case `FRAGMENT_DEFINITION`:
return LocationFragmentDefinition
case `FRAGMENT_SPREAD`:
return LocationFragmentSpread
case `INLINE_FRAGMENT`:
return LocationInlineFragment
case `VARIABLE_DEFINITION`:
return LocationVariableDefinition
case `SCHEMA`:
return LocationSchema
case `SCALAR`:
return LocationScalar
case `OBJECT`:
return LocationObject
case `FIELD_DEFINITION`:
return LocationFieldDefinition
case `ARGUMENT_DEFINITION`:
return LocationArgumentDefinition
case `INTERFACE`:
return LocationInterface
case `UNION`:
return LocationUnion
case `ENUM`:
return LocationEnum
case `ENUM_VALUE`:
return LocationEnumValue
case `INPUT_OBJECT`:
return LocationInputObject
case `INPUT_FIELD_DEFINITION`:
return LocationInputFieldDefinition
}
p.unexpectedToken(name)
return ""
}
@@ -1,646 +0,0 @@
object types:
- name: simple
input: |
type Hello {
world: String
}
ast: |
<SchemaDocument>
Definitions: [Definition]
- <Definition>
Kind: DefinitionKind("OBJECT")
Name: "Hello"
Fields: [FieldDefinition]
- <FieldDefinition>
Name: "world"
Type: String
- name: with description
input: |
"Description"
type Hello {
world: String
}
ast: |
<SchemaDocument>
Definitions: [Definition]
- <Definition>
Kind: DefinitionKind("OBJECT")
Description: "Description"
Name: "Hello"
Fields: [FieldDefinition]
- <FieldDefinition>
Name: "world"
Type: String
- name: with block description
input: |
"""
Description
"""
# Even with comments between them
type Hello {
world: String
}
ast: |
<SchemaDocument>
Definitions: [Definition]
- <Definition>
Kind: DefinitionKind("OBJECT")
Description: "Description"
Name: "Hello"
Fields: [FieldDefinition]
- <FieldDefinition>
Name: "world"
Type: String
- name: with field arg
input: |
type Hello {
world(flag: Boolean): String
}
ast: |
<SchemaDocument>
Definitions: [Definition]
- <Definition>
Kind: DefinitionKind("OBJECT")
Name: "Hello"
Fields: [FieldDefinition]
- <FieldDefinition>
Name: "world"
Arguments: [ArgumentDefinition]
- <ArgumentDefinition>
Name: "flag"
Type: Boolean
Type: String
- name: with field arg and default value
input: |
type Hello {
world(flag: Boolean = true): String
}
ast: |
<SchemaDocument>
Definitions: [Definition]
- <Definition>
Kind: DefinitionKind("OBJECT")
Name: "Hello"
Fields: [FieldDefinition]
- <FieldDefinition>
Name: "world"
Arguments: [ArgumentDefinition]
- <ArgumentDefinition>
Name: "flag"
DefaultValue: true
Type: Boolean
Type: String
- name: with field list arg
input: |
type Hello {
world(things: [String]): String
}
ast: |
<SchemaDocument>
Definitions: [Definition]
- <Definition>
Kind: DefinitionKind("OBJECT")
Name: "Hello"
Fields: [FieldDefinition]
- <FieldDefinition>
Name: "world"
Arguments: [ArgumentDefinition]
- <ArgumentDefinition>
Name: "things"
Type: [String]
Type: String
- name: with two args
input: |
type Hello {
world(argOne: Boolean, argTwo: Int): String
}
ast: |
<SchemaDocument>
Definitions: [Definition]
- <Definition>
Kind: DefinitionKind("OBJECT")
Name: "Hello"
Fields: [FieldDefinition]
- <FieldDefinition>
Name: "world"
Arguments: [ArgumentDefinition]
- <ArgumentDefinition>
Name: "argOne"
Type: Boolean
- <ArgumentDefinition>
Name: "argTwo"
Type: Int
Type: String
- name: must define one or more fields
input: |
type Hello {}
error:
message: "expected at least one definition, found }"
locations: [{ line: 1, column: 13 }]
type extensions:
- name: Object extension
input: |
extend type Hello {
world: String
}
ast: |
<SchemaDocument>
Extensions: [Definition]
- <Definition>
Kind: DefinitionKind("OBJECT")
Name: "Hello"
Fields: [FieldDefinition]
- <FieldDefinition>
Name: "world"
Type: String
- name: without any fields
input: "extend type Hello implements Greeting"
ast: |
<SchemaDocument>
Extensions: [Definition]
- <Definition>
Kind: DefinitionKind("OBJECT")
Name: "Hello"
Interfaces: [string]
- "Greeting"
- name: without fields twice
input: |
extend type Hello implements Greeting
extend type Hello implements SecondGreeting
ast: |
<SchemaDocument>
Extensions: [Definition]
- <Definition>
Kind: DefinitionKind("OBJECT")
Name: "Hello"
Interfaces: [string]
- "Greeting"
- <Definition>
Kind: DefinitionKind("OBJECT")
Name: "Hello"
Interfaces: [string]
- "SecondGreeting"
- name: without anything errors
input: "extend type Hello"
error:
message: "Unexpected <EOF>"
locations: [{ line: 1, column: 18 }]
- name: can have descriptions # hmm, this might not be spec compliant...
input: |
"Description"
extend type Hello {
world: String
}
error:
message: 'Unexpected String "Description"'
locations: [{ line: 1, column: 2 }]
- name: can not have descriptions on types
input: |
extend "Description" type Hello {
world: String
}
error:
message: Unexpected String "Description"
locations: [{ line: 1, column: 9 }]
- name: all can have directives
input: |
extend scalar Foo @deprecated
extend type Foo @deprecated
extend interface Foo @deprecated
extend union Foo @deprecated
extend enum Foo @deprecated
extend input Foo @deprecated
ast: |
<SchemaDocument>
Extensions: [Definition]
- <Definition>
Kind: DefinitionKind("SCALAR")
Name: "Foo"
Directives: [Directive]
- <Directive>
Name: "deprecated"
- <Definition>
Kind: DefinitionKind("OBJECT")
Name: "Foo"
Directives: [Directive]
- <Directive>
Name: "deprecated"
- <Definition>
Kind: DefinitionKind("INTERFACE")
Name: "Foo"
Directives: [Directive]
- <Directive>
Name: "deprecated"
- <Definition>
Kind: DefinitionKind("UNION")
Name: "Foo"
Directives: [Directive]
- <Directive>
Name: "deprecated"
- <Definition>
Kind: DefinitionKind("ENUM")
Name: "Foo"
Directives: [Directive]
- <Directive>
Name: "deprecated"
- <Definition>
Kind: DefinitionKind("INPUT_OBJECT")
Name: "Foo"
Directives: [Directive]
- <Directive>
Name: "deprecated"
schema definition:
- name: simple
input: |
schema {
query: Query
}
ast: |
<SchemaDocument>
Schema: [SchemaDefinition]
- <SchemaDefinition>
OperationTypes: [OperationTypeDefinition]
- <OperationTypeDefinition>
Operation: Operation("query")
Type: "Query"
schema extensions:
- name: simple
input: |
extend schema {
mutation: Mutation
}
ast: |
<SchemaDocument>
SchemaExtension: [SchemaDefinition]
- <SchemaDefinition>
OperationTypes: [OperationTypeDefinition]
- <OperationTypeDefinition>
Operation: Operation("mutation")
Type: "Mutation"
- name: directive only
input: "extend schema @directive"
ast: |
<SchemaDocument>
SchemaExtension: [SchemaDefinition]
- <SchemaDefinition>
Directives: [Directive]
- <Directive>
Name: "directive"
- name: without anything errors
input: "extend schema"
error:
message: "Unexpected <EOF>"
locations: [{ line: 1, column: 14}]
inheritance:
- name: single
input: "type Hello implements World { field: String }"
ast: |
<SchemaDocument>
Definitions: [Definition]
- <Definition>
Kind: DefinitionKind("OBJECT")
Name: "Hello"
Interfaces: [string]
- "World"
Fields: [FieldDefinition]
- <FieldDefinition>
Name: "field"
Type: String
- name: multi
input: "type Hello implements Wo & rld { field: String }"
ast: |
<SchemaDocument>
Definitions: [Definition]
- <Definition>
Kind: DefinitionKind("OBJECT")
Name: "Hello"
Interfaces: [string]
- "Wo"
- "rld"
Fields: [FieldDefinition]
- <FieldDefinition>
Name: "field"
Type: String
- name: multi with leading amp
input: "type Hello implements & Wo & rld { field: String }"
ast: |
<SchemaDocument>
Definitions: [Definition]
- <Definition>
Kind: DefinitionKind("OBJECT")
Name: "Hello"
Interfaces: [string]
- "Wo"
- "rld"
Fields: [FieldDefinition]
- <FieldDefinition>
Name: "field"
Type: String
enums:
- name: single value
input: "enum Hello { WORLD }"
ast: |
<SchemaDocument>
Definitions: [Definition]
- <Definition>
Kind: DefinitionKind("ENUM")
Name: "Hello"
EnumValues: [EnumValueDefinition]
- <EnumValueDefinition>
Name: "WORLD"
- name: double value
input: "enum Hello { WO, RLD }"
ast: |
<SchemaDocument>
Definitions: [Definition]
- <Definition>
Kind: DefinitionKind("ENUM")
Name: "Hello"
EnumValues: [EnumValueDefinition]
- <EnumValueDefinition>
Name: "WO"
- <EnumValueDefinition>
Name: "RLD"
- name: must define one or more unique enum values
input: |
enum Hello {}
error:
message: "expected at least one definition, found }"
locations: [{ line: 1, column: 13 }]
interface:
- name: simple
input: |
interface Hello {
world: String
}
ast: |
<SchemaDocument>
Definitions: [Definition]
- <Definition>
Kind: DefinitionKind("INTERFACE")
Name: "Hello"
Fields: [FieldDefinition]
- <FieldDefinition>
Name: "world"
Type: String
- name: must define one or more fields
input: |
interface Hello {}
error:
message: "expected at least one definition, found }"
locations: [{ line: 1, column: 18 }]
- name: may define intermediate interfaces
input: |
interface IA {
id: ID!
}
interface IIA implements IA {
id: ID!
}
type A implements IIA {
id: ID!
}
ast: |
<SchemaDocument>
Definitions: [Definition]
- <Definition>
Kind: DefinitionKind("INTERFACE")
Name: "IA"
Fields: [FieldDefinition]
- <FieldDefinition>
Name: "id"
Type: ID!
- <Definition>
Kind: DefinitionKind("INTERFACE")
Name: "IIA"
Interfaces: [string]
- "IA"
Fields: [FieldDefinition]
- <FieldDefinition>
Name: "id"
Type: ID!
- <Definition>
Kind: DefinitionKind("OBJECT")
Name: "A"
Interfaces: [string]
- "IIA"
Fields: [FieldDefinition]
- <FieldDefinition>
Name: "id"
Type: ID!
unions:
- name: simple
input: "union Hello = World"
ast: |
<SchemaDocument>
Definitions: [Definition]
- <Definition>
Kind: DefinitionKind("UNION")
Name: "Hello"
Types: [string]
- "World"
- name: with two types
input: "union Hello = Wo | Rld"
ast: |
<SchemaDocument>
Definitions: [Definition]
- <Definition>
Kind: DefinitionKind("UNION")
Name: "Hello"
Types: [string]
- "Wo"
- "Rld"
- name: with leading pipe
input: "union Hello = | Wo | Rld"
ast: |
<SchemaDocument>
Definitions: [Definition]
- <Definition>
Kind: DefinitionKind("UNION")
Name: "Hello"
Types: [string]
- "Wo"
- "Rld"
- name: cant be empty
input: "union Hello = || Wo | Rld"
error:
message: "Expected Name, found |"
locations: [{ line: 1, column: 16 }]
- name: cant double pipe
input: "union Hello = Wo || Rld"
error:
message: "Expected Name, found |"
locations: [{ line: 1, column: 19 }]
- name: cant have trailing pipe
input: "union Hello = | Wo | Rld |"
error:
message: "Expected Name, found <EOF>"
locations: [{ line: 1, column: 27 }]
scalar:
- name: simple
input: "scalar Hello"
ast: |
<SchemaDocument>
Definitions: [Definition]
- <Definition>
Kind: DefinitionKind("SCALAR")
Name: "Hello"
input object:
- name: simple
input: |
input Hello {
world: String
}
ast: |
<SchemaDocument>
Definitions: [Definition]
- <Definition>
Kind: DefinitionKind("INPUT_OBJECT")
Name: "Hello"
Fields: [FieldDefinition]
- <FieldDefinition>
Name: "world"
Type: String
- name: can not have args
input: |
input Hello {
world(foo: Int): String
}
error:
message: "Expected :, found ("
locations: [{ line: 2, column: 8 }]
- name: must define one or more input fields
input: |
input Hello {}
error:
message: "expected at least one definition, found }"
locations: [{ line: 1, column: 14 }]
directives:
- name: simple
input: directive @foo on FIELD
ast: |
<SchemaDocument>
Directives: [DirectiveDefinition]
- <DirectiveDefinition>
Name: "foo"
Locations: [DirectiveLocation]
- DirectiveLocation("FIELD")
IsRepeatable: false
- name: executable
input: |
directive @onQuery on QUERY
directive @onMutation on MUTATION
directive @onSubscription on SUBSCRIPTION
directive @onField on FIELD
directive @onFragmentDefinition on FRAGMENT_DEFINITION
directive @onFragmentSpread on FRAGMENT_SPREAD
directive @onInlineFragment on INLINE_FRAGMENT
directive @onVariableDefinition on VARIABLE_DEFINITION
ast: |
<SchemaDocument>
Directives: [DirectiveDefinition]
- <DirectiveDefinition>
Name: "onQuery"
Locations: [DirectiveLocation]
- DirectiveLocation("QUERY")
IsRepeatable: false
- <DirectiveDefinition>
Name: "onMutation"
Locations: [DirectiveLocation]
- DirectiveLocation("MUTATION")
IsRepeatable: false
- <DirectiveDefinition>
Name: "onSubscription"
Locations: [DirectiveLocation]
- DirectiveLocation("SUBSCRIPTION")
IsRepeatable: false
- <DirectiveDefinition>
Name: "onField"
Locations: [DirectiveLocation]
- DirectiveLocation("FIELD")
IsRepeatable: false
- <DirectiveDefinition>
Name: "onFragmentDefinition"
Locations: [DirectiveLocation]
- DirectiveLocation("FRAGMENT_DEFINITION")
IsRepeatable: false
- <DirectiveDefinition>
Name: "onFragmentSpread"
Locations: [DirectiveLocation]
- DirectiveLocation("FRAGMENT_SPREAD")
IsRepeatable: false
- <DirectiveDefinition>
Name: "onInlineFragment"
Locations: [DirectiveLocation]
- DirectiveLocation("INLINE_FRAGMENT")
IsRepeatable: false
- <DirectiveDefinition>
Name: "onVariableDefinition"
Locations: [DirectiveLocation]
- DirectiveLocation("VARIABLE_DEFINITION")
IsRepeatable: false
- name: repeatable
input: directive @foo repeatable on FIELD
ast: |
<SchemaDocument>
Directives: [DirectiveDefinition]
- <DirectiveDefinition>
Name: "foo"
Locations: [DirectiveLocation]
- DirectiveLocation("FIELD")
IsRepeatable: true
- name: invalid location
input: "directive @foo on FIELD | INCORRECT_LOCATION"
error:
message: 'Unexpected Name "INCORRECT_LOCATION"'
locations: [{ line: 1, column: 27 }]
fuzzer:
- name: 1
input: "type o{d(g:["
error:
message: 'Expected Name, found <EOF>'
locations: [{ line: 1, column: 13 }]
- name: 2
input: "\"\"\"\r"
error:
message: 'Unexpected <Invalid>'
locations: [{ line: 2, column: 1 }]
@@ -1,55 +0,0 @@
package validator
import (
"fmt"
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
"github.com/open-policy-agent/opa/internal/gqlparser/gqlerror"
)
type ErrorOption func(err *gqlerror.Error)
func Message(msg string, args ...interface{}) ErrorOption {
return func(err *gqlerror.Error) {
err.Message += fmt.Sprintf(msg, args...)
}
}
func At(position *ast.Position) ErrorOption {
return func(err *gqlerror.Error) {
if position == nil {
return
}
err.Locations = append(err.Locations, gqlerror.Location{
Line: position.Line,
Column: position.Column,
})
if position.Src.Name != "" {
err.SetFile(position.Src.Name)
}
}
}
func SuggestListQuoted(prefix string, typed string, suggestions []string) ErrorOption {
suggested := SuggestionList(typed, suggestions)
return func(err *gqlerror.Error) {
if len(suggested) > 0 {
err.Message += " " + prefix + " " + QuotedOrList(suggested...) + "?"
}
}
}
func SuggestListUnquoted(prefix string, typed string, suggestions []string) ErrorOption {
suggested := SuggestionList(typed, suggestions)
return func(err *gqlerror.Error) {
if len(suggested) > 0 {
err.Message += " " + prefix + " " + OrList(suggested...) + "?"
}
}
}
func Suggestf(suggestion string, args ...interface{}) ErrorOption {
return func(err *gqlerror.Error) {
err.Message += " Did you mean " + fmt.Sprintf(suggestion, args...) + "?"
}
}
@@ -1,39 +0,0 @@
package validator
import "bytes"
// Given [ A, B, C ] return '"A", "B", or "C"'.
func QuotedOrList(items ...string) string {
itemsQuoted := make([]string, len(items))
for i, item := range items {
itemsQuoted[i] = `"` + item + `"`
}
return OrList(itemsQuoted...)
}
// Given [ A, B, C ] return 'A, B, or C'.
func OrList(items ...string) string {
var buf bytes.Buffer
if len(items) > 5 {
items = items[:5]
}
if len(items) == 2 {
buf.WriteString(items[0])
buf.WriteString(" or ")
buf.WriteString(items[1])
return buf.String()
}
for i, item := range items {
if i != 0 {
if i == len(items)-1 {
buf.WriteString(", or ")
} else {
buf.WriteString(", ")
}
}
buf.WriteString(item)
}
return buf.String()
}
@@ -1,16 +0,0 @@
package validator
import (
_ "embed"
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
)
//go:embed prelude.graphql
var preludeGraphql string
var Prelude = &ast.Source{
Name: "prelude.graphql",
Input: preludeGraphql,
BuiltIn: true,
}
@@ -1,121 +0,0 @@
# This file defines all the implicitly declared types that are required by the graphql spec. It is implicitly included by calls to LoadSchema
"The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1."
scalar Int
"The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point)."
scalar Float
"The `String`scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text."
scalar String
"The `Boolean` scalar type represents `true` or `false`."
scalar Boolean
"""The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as "4") or integer (such as 4) input value will be accepted as an ID."""
scalar ID
"The @include directive may be provided for fields, fragment spreads, and inline fragments, and allows for conditional inclusion during execution as described by the if argument."
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
"The @skip directive may be provided for fields, fragment spreads, and inline fragments, and allows for conditional exclusion during execution as described by the if argument."
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
"The @deprecated built-in directive is used within the type system definition language to indicate deprecated portions of a GraphQL service's schema, such as deprecated fields on a type, arguments on a field, input fields on an input type, or values of an enum type."
directive @deprecated(reason: String = "No longer supported") on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION | ENUM_VALUE
"The @specifiedBy built-in directive is used within the type system definition language to provide a scalar specification URL for specifying the behavior of custom scalar types."
directive @specifiedBy(url: String!) on SCALAR
type __Schema {
description: String
types: [__Type!]!
queryType: __Type!
mutationType: __Type
subscriptionType: __Type
directives: [__Directive!]!
}
type __Type {
kind: __TypeKind!
name: String
description: String
# must be non-null for OBJECT and INTERFACE, otherwise null.
fields(includeDeprecated: Boolean = false): [__Field!]
# must be non-null for OBJECT and INTERFACE, otherwise null.
interfaces: [__Type!]
# must be non-null for INTERFACE and UNION, otherwise null.
possibleTypes: [__Type!]
# must be non-null for ENUM, otherwise null.
enumValues(includeDeprecated: Boolean = false): [__EnumValue!]
# must be non-null for INPUT_OBJECT, otherwise null.
inputFields: [__InputValue!]
# must be non-null for NON_NULL and LIST, otherwise null.
ofType: __Type
# may be non-null for custom SCALAR, otherwise null.
specifiedByURL: String
}
type __Field {
name: String!
description: String
args: [__InputValue!]!
type: __Type!
isDeprecated: Boolean!
deprecationReason: String
}
type __InputValue {
name: String!
description: String
type: __Type!
defaultValue: String
}
type __EnumValue {
name: String!
description: String
isDeprecated: Boolean!
deprecationReason: String
}
enum __TypeKind {
SCALAR
OBJECT
INTERFACE
UNION
ENUM
INPUT_OBJECT
LIST
NON_NULL
}
type __Directive {
name: String!
description: String
locations: [__DirectiveLocation!]!
args: [__InputValue!]!
isRepeatable: Boolean!
}
enum __DirectiveLocation {
QUERY
MUTATION
SUBSCRIPTION
FIELD
FRAGMENT_DEFINITION
FRAGMENT_SPREAD
INLINE_FRAGMENT
VARIABLE_DEFINITION
SCHEMA
SCALAR
OBJECT
FIELD_DEFINITION
ARGUMENT_DEFINITION
INTERFACE
UNION
ENUM
ENUM_VALUE
INPUT_OBJECT
INPUT_FIELD_DEFINITION
}
@@ -1,97 +0,0 @@
package validator
import (
"fmt"
"sort"
"strings"
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("FieldsOnCorrectType", func(observers *Events, addError AddErrFunc) {
observers.OnField(func(walker *Walker, field *ast.Field) {
if field.ObjectDefinition == nil || field.Definition != nil {
return
}
message := fmt.Sprintf(`Cannot query field "%s" on type "%s".`, field.Name, field.ObjectDefinition.Name)
if suggestedTypeNames := getSuggestedTypeNames(walker, field.ObjectDefinition, field.Name); suggestedTypeNames != nil {
message += " Did you mean to use an inline fragment on " + QuotedOrList(suggestedTypeNames...) + "?"
} else if suggestedFieldNames := getSuggestedFieldNames(field.ObjectDefinition, field.Name); suggestedFieldNames != nil {
message += " Did you mean " + QuotedOrList(suggestedFieldNames...) + "?"
}
addError(
Message(message), //nolint:govet
At(field.Position),
)
})
})
}
// Go through all of the implementations of type, as well as the interfaces
// that they implement. If any of those types include the provided field,
// suggest them, sorted by how often the type is referenced, starting
// with Interfaces.
func getSuggestedTypeNames(walker *Walker, parent *ast.Definition, name string) []string {
if !parent.IsAbstractType() {
return nil
}
possibleTypes := walker.Schema.GetPossibleTypes(parent)
var suggestedObjectTypes = make([]string, 0, len(possibleTypes))
var suggestedInterfaceTypes []string
interfaceUsageCount := map[string]int{}
for _, possibleType := range possibleTypes {
field := possibleType.Fields.ForName(name)
if field == nil {
continue
}
suggestedObjectTypes = append(suggestedObjectTypes, possibleType.Name)
for _, possibleInterface := range possibleType.Interfaces {
interfaceField := walker.Schema.Types[possibleInterface]
if interfaceField != nil && interfaceField.Fields.ForName(name) != nil {
if interfaceUsageCount[possibleInterface] == 0 {
suggestedInterfaceTypes = append(suggestedInterfaceTypes, possibleInterface)
}
interfaceUsageCount[possibleInterface]++
}
}
}
suggestedTypes := append(suggestedInterfaceTypes, suggestedObjectTypes...)
sort.SliceStable(suggestedTypes, func(i, j int) bool {
typeA, typeB := suggestedTypes[i], suggestedTypes[j]
diff := interfaceUsageCount[typeB] - interfaceUsageCount[typeA]
if diff != 0 {
return diff < 0
}
return strings.Compare(typeA, typeB) < 0
})
return suggestedTypes
}
// For the field name provided, determine if there are any similar field names
// that may be the result of a typo.
func getSuggestedFieldNames(parent *ast.Definition, name string) []string {
if parent.Kind != ast.Object && parent.Kind != ast.Interface {
return nil
}
var possibleFieldNames = make([]string, 0, len(parent.Fields))
for _, field := range parent.Fields {
possibleFieldNames = append(possibleFieldNames, field.Name)
}
return SuggestionList(name, possibleFieldNames)
}
@@ -1,41 +0,0 @@
package validator
import (
"fmt"
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("FragmentsOnCompositeTypes", func(observers *Events, addError AddErrFunc) {
observers.OnInlineFragment(func(walker *Walker, inlineFragment *ast.InlineFragment) {
fragmentType := walker.Schema.Types[inlineFragment.TypeCondition]
if fragmentType == nil || fragmentType.IsCompositeType() {
return
}
message := fmt.Sprintf(`Fragment cannot condition on non composite type "%s".`, inlineFragment.TypeCondition)
addError(
Message(message), //nolint:govet
At(inlineFragment.Position),
)
})
observers.OnFragment(func(_ *Walker, fragment *ast.FragmentDefinition) {
if fragment.Definition == nil || fragment.TypeCondition == "" || fragment.Definition.IsCompositeType() {
return
}
message := fmt.Sprintf(`Fragment "%s" cannot condition on non composite type "%s".`, fragment.Name, fragment.TypeCondition)
addError(
Message(message), //nolint:govet
At(fragment.Position),
)
})
})
}
@@ -1,59 +0,0 @@
package validator
import (
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("KnownArgumentNames", func(observers *Events, addError AddErrFunc) {
// A GraphQL field is only valid if all supplied arguments are defined by that field.
observers.OnField(func(_ *Walker, field *ast.Field) {
if field.Definition == nil || field.ObjectDefinition == nil {
return
}
for _, arg := range field.Arguments {
def := field.Definition.Arguments.ForName(arg.Name)
if def != nil {
continue
}
var suggestions []string
for _, argDef := range field.Definition.Arguments {
suggestions = append(suggestions, argDef.Name)
}
addError(
Message(`Unknown argument "%s" on field "%s.%s".`, arg.Name, field.ObjectDefinition.Name, field.Name),
SuggestListQuoted("Did you mean", arg.Name, suggestions),
At(field.Position),
)
}
})
observers.OnDirective(func(_ *Walker, directive *ast.Directive) {
if directive.Definition == nil {
return
}
for _, arg := range directive.Arguments {
def := directive.Definition.Arguments.ForName(arg.Name)
if def != nil {
continue
}
var suggestions []string
for _, argDef := range directive.Definition.Arguments {
suggestions = append(suggestions, argDef.Name)
}
addError(
Message(`Unknown argument "%s" on directive "@%s".`, arg.Name, directive.Name),
SuggestListQuoted("Did you mean", arg.Name, suggestions),
At(directive.Position),
)
}
})
})
}
@@ -1,49 +0,0 @@
package validator
import (
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("KnownDirectives", func(observers *Events, addError AddErrFunc) {
type mayNotBeUsedDirective struct {
Name string
Line int
Column int
}
var seen = map[mayNotBeUsedDirective]bool{}
observers.OnDirective(func(_ *Walker, directive *ast.Directive) {
if directive.Definition == nil {
addError(
Message(`Unknown directive "@%s".`, directive.Name),
At(directive.Position),
)
return
}
for _, loc := range directive.Definition.Locations {
if loc == directive.Location {
return
}
}
// position must be exists if directive.Definition != nil
tmp := mayNotBeUsedDirective{
Name: directive.Name,
Line: directive.Position.Line,
Column: directive.Position.Column,
}
if !seen[tmp] {
addError(
Message(`Directive "@%s" may not be used on %s.`, directive.Name, directive.Location),
At(directive.Position),
)
seen[tmp] = true
}
})
})
}
@@ -1,21 +0,0 @@
package validator
import (
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("KnownFragmentNames", func(observers *Events, addError AddErrFunc) {
observers.OnFragmentSpread(func(_ *Walker, fragmentSpread *ast.FragmentSpread) {
if fragmentSpread.Definition == nil {
addError(
Message(`Unknown fragment "%s".`, fragmentSpread.Name),
At(fragmentSpread.Position),
)
}
})
})
}
@@ -1,37 +0,0 @@
package validator
import (
"fmt"
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("KnownRootType", func(observers *Events, addError AddErrFunc) {
// A query's root must be a valid type. Surprisingly, this isn't
// checked anywhere else!
observers.OnOperation(func(walker *Walker, operation *ast.OperationDefinition) {
var def *ast.Definition
switch operation.Operation {
case ast.Query, "":
def = walker.Schema.Query
case ast.Mutation:
def = walker.Schema.Mutation
case ast.Subscription:
def = walker.Schema.Subscription
default:
// This shouldn't even parse; if it did we probably need to
// update this switch block to add the new operation type.
panic(fmt.Sprintf(`got unknown operation type "%s"`, operation.Operation))
}
if def == nil {
addError(
Message(`Schema does not support operation type "%s"`, operation.Operation),
At(operation.Position))
}
})
})
}
@@ -1,61 +0,0 @@
package validator
import (
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("KnownTypeNames", func(observers *Events, addError AddErrFunc) {
observers.OnVariable(func(walker *Walker, variable *ast.VariableDefinition) {
typeName := variable.Type.Name()
typdef := walker.Schema.Types[typeName]
if typdef != nil {
return
}
addError(
Message(`Unknown type "%s".`, typeName),
At(variable.Position),
)
})
observers.OnInlineFragment(func(walker *Walker, inlineFragment *ast.InlineFragment) {
typedName := inlineFragment.TypeCondition
if typedName == "" {
return
}
def := walker.Schema.Types[typedName]
if def != nil {
return
}
addError(
Message(`Unknown type "%s".`, typedName),
At(inlineFragment.Position),
)
})
observers.OnFragment(func(walker *Walker, fragment *ast.FragmentDefinition) {
typeName := fragment.TypeCondition
def := walker.Schema.Types[typeName]
if def != nil {
return
}
var possibleTypes []string
for _, t := range walker.Schema.Types {
possibleTypes = append(possibleTypes, t.Name)
}
addError(
Message(`Unknown type "%s".`, typeName),
SuggestListQuoted("Did you mean", typeName, possibleTypes),
At(fragment.Position),
)
})
})
}
@@ -1,21 +0,0 @@
package validator
import (
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("LoneAnonymousOperation", func(observers *Events, addError AddErrFunc) {
observers.OnOperation(func(walker *Walker, operation *ast.OperationDefinition) {
if operation.Name == "" && len(walker.Document.Operations) > 1 {
addError(
Message(`This anonymous operation must be the only defined operation.`),
At(operation.Position),
)
}
})
})
}
@@ -1,95 +0,0 @@
package validator
import (
"fmt"
"strings"
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("NoFragmentCycles", func(observers *Events, addError AddErrFunc) {
visitedFrags := make(map[string]bool)
observers.OnFragment(func(walker *Walker, fragment *ast.FragmentDefinition) {
var spreadPath []*ast.FragmentSpread
spreadPathIndexByName := make(map[string]int)
var recursive func(fragment *ast.FragmentDefinition)
recursive = func(fragment *ast.FragmentDefinition) {
if visitedFrags[fragment.Name] {
return
}
visitedFrags[fragment.Name] = true
spreadNodes := getFragmentSpreads(fragment.SelectionSet)
if len(spreadNodes) == 0 {
return
}
spreadPathIndexByName[fragment.Name] = len(spreadPath)
for _, spreadNode := range spreadNodes {
spreadName := spreadNode.Name
cycleIndex, ok := spreadPathIndexByName[spreadName]
spreadPath = append(spreadPath, spreadNode)
if !ok {
spreadFragment := walker.Document.Fragments.ForName(spreadName)
if spreadFragment != nil {
recursive(spreadFragment)
}
} else {
cyclePath := spreadPath[cycleIndex : len(spreadPath)-1]
var fragmentNames []string
for _, fs := range cyclePath {
fragmentNames = append(fragmentNames, fmt.Sprintf(`"%s"`, fs.Name))
}
var via string
if len(fragmentNames) != 0 {
via = " via " + strings.Join(fragmentNames, ", ")
}
addError(
Message(`Cannot spread fragment "%s" within itself%s.`, spreadName, via),
At(spreadNode.Position),
)
}
spreadPath = spreadPath[:len(spreadPath)-1]
}
delete(spreadPathIndexByName, fragment.Name)
}
recursive(fragment)
})
})
}
func getFragmentSpreads(node ast.SelectionSet) []*ast.FragmentSpread {
var spreads []*ast.FragmentSpread
setsToVisit := []ast.SelectionSet{node}
for len(setsToVisit) != 0 {
set := setsToVisit[len(setsToVisit)-1]
setsToVisit = setsToVisit[:len(setsToVisit)-1]
for _, selection := range set {
switch selection := selection.(type) {
case *ast.FragmentSpread:
spreads = append(spreads, selection)
case *ast.Field:
setsToVisit = append(setsToVisit, selection.SelectionSet)
case *ast.InlineFragment:
setsToVisit = append(setsToVisit, selection.SelectionSet)
}
}
}
return spreads
}
@@ -1,30 +0,0 @@
package validator
import (
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("NoUndefinedVariables", func(observers *Events, addError AddErrFunc) {
observers.OnValue(func(walker *Walker, value *ast.Value) {
if walker.CurrentOperation == nil || value.Kind != ast.Variable || value.VariableDefinition != nil {
return
}
if walker.CurrentOperation.Name != "" {
addError(
Message(`Variable "%s" is not defined by operation "%s".`, value, walker.CurrentOperation.Name),
At(value.Position),
)
} else {
addError(
Message(`Variable "%s" is not defined.`, value),
At(value.Position),
)
}
})
})
}
@@ -1,32 +0,0 @@
package validator
import (
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("NoUnusedFragments", func(observers *Events, addError AddErrFunc) {
inFragmentDefinition := false
fragmentNameUsed := make(map[string]bool)
observers.OnFragmentSpread(func(_ *Walker, fragmentSpread *ast.FragmentSpread) {
if !inFragmentDefinition {
fragmentNameUsed[fragmentSpread.Name] = true
}
})
observers.OnFragment(func(_ *Walker, fragment *ast.FragmentDefinition) {
inFragmentDefinition = true
if !fragmentNameUsed[fragment.Name] {
addError(
Message(`Fragment "%s" is never used.`, fragment.Name),
At(fragment.Position),
)
}
})
})
}
@@ -1,32 +0,0 @@
package validator
import (
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("NoUnusedVariables", func(observers *Events, addError AddErrFunc) {
observers.OnOperation(func(_ *Walker, operation *ast.OperationDefinition) {
for _, varDef := range operation.VariableDefinitions {
if varDef.Used {
continue
}
if operation.Name != "" {
addError(
Message(`Variable "$%s" is never used in operation "%s".`, varDef.Variable, operation.Name),
At(varDef.Position),
)
} else {
addError(
Message(`Variable "$%s" is never used.`, varDef.Variable),
At(varDef.Position),
)
}
}
})
})
}
@@ -1,562 +0,0 @@
package validator
import (
"bytes"
"fmt"
"reflect"
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("OverlappingFieldsCanBeMerged", func(observers *Events, addError AddErrFunc) {
/**
* Algorithm:
*
* Conflicts occur when two fields exist in a query which will produce the same
* response name, but represent differing values, thus creating a conflict.
* The algorithm below finds all conflicts via making a series of comparisons
* between fields. In order to compare as few fields as possible, this makes
* a series of comparisons "within" sets of fields and "between" sets of fields.
*
* Given any selection set, a collection produces both a set of fields by
* also including all inline fragments, as well as a list of fragments
* referenced by fragment spreads.
*
* A) Each selection set represented in the document first compares "within" its
* collected set of fields, finding any conflicts between every pair of
* overlapping fields.
* Note: This is the *only time* that a the fields "within" a set are compared
* to each other. After this only fields "between" sets are compared.
*
* B) Also, if any fragment is referenced in a selection set, then a
* comparison is made "between" the original set of fields and the
* referenced fragment.
*
* C) Also, if multiple fragments are referenced, then comparisons
* are made "between" each referenced fragment.
*
* D) When comparing "between" a set of fields and a referenced fragment, first
* a comparison is made between each field in the original set of fields and
* each field in the referenced set of fields.
*
* E) Also, if any fragment is referenced in the referenced selection set,
* then a comparison is made "between" the original set of fields and the
* referenced fragment (recursively referring to step D).
*
* F) When comparing "between" two fragments, first a comparison is made between
* each field in the first referenced set of fields and each field in the the
* second referenced set of fields.
*
* G) Also, any fragments referenced by the first must be compared to the
* second, and any fragments referenced by the second must be compared to the
* first (recursively referring to step F).
*
* H) When comparing two fields, if both have selection sets, then a comparison
* is made "between" both selection sets, first comparing the set of fields in
* the first selection set with the set of fields in the second.
*
* I) Also, if any fragment is referenced in either selection set, then a
* comparison is made "between" the other set of fields and the
* referenced fragment.
*
* J) Also, if two fragments are referenced in both selection sets, then a
* comparison is made "between" the two fragments.
*
*/
m := &overlappingFieldsCanBeMergedManager{
comparedFragmentPairs: pairSet{data: make(map[string]map[string]bool)},
}
observers.OnOperation(func(walker *Walker, operation *ast.OperationDefinition) {
m.walker = walker
conflicts := m.findConflictsWithinSelectionSet(operation.SelectionSet)
for _, conflict := range conflicts {
conflict.addFieldsConflictMessage(addError)
}
})
observers.OnField(func(walker *Walker, field *ast.Field) {
if walker.CurrentOperation == nil {
// When checking both Operation and Fragment, errors are duplicated when processing FragmentDefinition referenced from Operation
return
}
m.walker = walker
conflicts := m.findConflictsWithinSelectionSet(field.SelectionSet)
for _, conflict := range conflicts {
conflict.addFieldsConflictMessage(addError)
}
})
observers.OnInlineFragment(func(walker *Walker, inlineFragment *ast.InlineFragment) {
m.walker = walker
conflicts := m.findConflictsWithinSelectionSet(inlineFragment.SelectionSet)
for _, conflict := range conflicts {
conflict.addFieldsConflictMessage(addError)
}
})
observers.OnFragment(func(walker *Walker, fragment *ast.FragmentDefinition) {
m.walker = walker
conflicts := m.findConflictsWithinSelectionSet(fragment.SelectionSet)
for _, conflict := range conflicts {
conflict.addFieldsConflictMessage(addError)
}
})
})
}
type pairSet struct {
data map[string]map[string]bool
}
func (pairSet *pairSet) Add(a *ast.FragmentSpread, b *ast.FragmentSpread, areMutuallyExclusive bool) {
add := func(a *ast.FragmentSpread, b *ast.FragmentSpread) {
m := pairSet.data[a.Name]
if m == nil {
m = make(map[string]bool)
pairSet.data[a.Name] = m
}
m[b.Name] = areMutuallyExclusive
}
add(a, b)
add(b, a)
}
func (pairSet *pairSet) Has(a *ast.FragmentSpread, b *ast.FragmentSpread, areMutuallyExclusive bool) bool {
am, ok := pairSet.data[a.Name]
if !ok {
return false
}
result, ok := am[b.Name]
if !ok {
return false
}
// areMutuallyExclusive being false is a superset of being true,
// hence if we want to know if this PairSet "has" these two with no
// exclusivity, we have to ensure it was added as such.
if !areMutuallyExclusive {
return !result
}
return true
}
type sequentialFieldsMap struct {
// We can't use map[string][]*ast.Field. because map is not stable...
seq []string
data map[string][]*ast.Field
}
type fieldIterateEntry struct {
ResponseName string
Fields []*ast.Field
}
func (m *sequentialFieldsMap) Push(responseName string, field *ast.Field) {
fields, ok := m.data[responseName]
if !ok {
m.seq = append(m.seq, responseName)
}
fields = append(fields, field)
m.data[responseName] = fields
}
func (m *sequentialFieldsMap) Get(responseName string) ([]*ast.Field, bool) {
fields, ok := m.data[responseName]
return fields, ok
}
func (m *sequentialFieldsMap) Iterator() [][]*ast.Field {
fieldsList := make([][]*ast.Field, 0, len(m.seq))
for _, responseName := range m.seq {
fields := m.data[responseName]
fieldsList = append(fieldsList, fields)
}
return fieldsList
}
func (m *sequentialFieldsMap) KeyValueIterator() []*fieldIterateEntry {
fieldEntriesList := make([]*fieldIterateEntry, 0, len(m.seq))
for _, responseName := range m.seq {
fields := m.data[responseName]
fieldEntriesList = append(fieldEntriesList, &fieldIterateEntry{
ResponseName: responseName,
Fields: fields,
})
}
return fieldEntriesList
}
type conflictMessageContainer struct {
Conflicts []*ConflictMessage
}
type ConflictMessage struct {
Message string
ResponseName string
Names []string
SubMessage []*ConflictMessage
Position *ast.Position
}
func (m *ConflictMessage) String(buf *bytes.Buffer) {
if len(m.SubMessage) == 0 {
buf.WriteString(m.Message)
return
}
for idx, subMessage := range m.SubMessage {
buf.WriteString(`subfields "`)
buf.WriteString(subMessage.ResponseName)
buf.WriteString(`" conflict because `)
subMessage.String(buf)
if idx != len(m.SubMessage)-1 {
buf.WriteString(" and ")
}
}
}
func (m *ConflictMessage) addFieldsConflictMessage(addError AddErrFunc) {
var buf bytes.Buffer
m.String(&buf)
addError(
Message(`Fields "%s" conflict because %s. Use different aliases on the fields to fetch both if this was intentional.`, m.ResponseName, buf.String()),
At(m.Position),
)
}
type overlappingFieldsCanBeMergedManager struct {
walker *Walker
// per walker
comparedFragmentPairs pairSet
// cachedFieldsAndFragmentNames interface{}
// per selectionSet
comparedFragments map[string]bool
}
func (m *overlappingFieldsCanBeMergedManager) findConflictsWithinSelectionSet(selectionSet ast.SelectionSet) []*ConflictMessage {
if len(selectionSet) == 0 {
return nil
}
fieldsMap, fragmentSpreads := getFieldsAndFragmentNames(selectionSet)
var conflicts conflictMessageContainer
// (A) Find find all conflicts "within" the fieldMap of this selection set.
// Note: this is the *only place* `collectConflictsWithin` is called.
m.collectConflictsWithin(&conflicts, fieldsMap)
m.comparedFragments = make(map[string]bool)
for idx, fragmentSpreadA := range fragmentSpreads {
// (B) Then collect conflicts between these fieldMap and those represented by
// each spread fragment name found.
m.collectConflictsBetweenFieldsAndFragment(&conflicts, false, fieldsMap, fragmentSpreadA)
for _, fragmentSpreadB := range fragmentSpreads[idx+1:] {
// (C) Then compare this fragment with all other fragments found in this
// selection set to collect conflicts between fragments spread together.
// This compares each item in the list of fragment names to every other
// item in that same list (except for itself).
m.collectConflictsBetweenFragments(&conflicts, false, fragmentSpreadA, fragmentSpreadB)
}
}
return conflicts.Conflicts
}
func (m *overlappingFieldsCanBeMergedManager) collectConflictsBetweenFieldsAndFragment(conflicts *conflictMessageContainer, areMutuallyExclusive bool, fieldsMap *sequentialFieldsMap, fragmentSpread *ast.FragmentSpread) {
if m.comparedFragments[fragmentSpread.Name] {
return
}
m.comparedFragments[fragmentSpread.Name] = true
if fragmentSpread.Definition == nil {
return
}
fieldsMapB, fragmentSpreads := getFieldsAndFragmentNames(fragmentSpread.Definition.SelectionSet)
// Do not compare a fragment's fieldMap to itself.
if reflect.DeepEqual(fieldsMap, fieldsMapB) {
return
}
// (D) First collect any conflicts between the provided collection of fields
// and the collection of fields represented by the given fragment.
m.collectConflictsBetween(conflicts, areMutuallyExclusive, fieldsMap, fieldsMapB)
// (E) Then collect any conflicts between the provided collection of fields
// and any fragment names found in the given fragment.
baseFragmentSpread := fragmentSpread
for _, fragmentSpread := range fragmentSpreads {
if fragmentSpread.Name == baseFragmentSpread.Name {
continue
}
m.collectConflictsBetweenFieldsAndFragment(conflicts, areMutuallyExclusive, fieldsMap, fragmentSpread)
}
}
func (m *overlappingFieldsCanBeMergedManager) collectConflictsBetweenFragments(conflicts *conflictMessageContainer, areMutuallyExclusive bool, fragmentSpreadA *ast.FragmentSpread, fragmentSpreadB *ast.FragmentSpread) {
var check func(fragmentSpreadA *ast.FragmentSpread, fragmentSpreadB *ast.FragmentSpread)
check = func(fragmentSpreadA *ast.FragmentSpread, fragmentSpreadB *ast.FragmentSpread) {
if fragmentSpreadA.Name == fragmentSpreadB.Name {
return
}
if m.comparedFragmentPairs.Has(fragmentSpreadA, fragmentSpreadB, areMutuallyExclusive) {
return
}
m.comparedFragmentPairs.Add(fragmentSpreadA, fragmentSpreadB, areMutuallyExclusive)
if fragmentSpreadA.Definition == nil {
return
}
if fragmentSpreadB.Definition == nil {
return
}
fieldsMapA, fragmentSpreadsA := getFieldsAndFragmentNames(fragmentSpreadA.Definition.SelectionSet)
fieldsMapB, fragmentSpreadsB := getFieldsAndFragmentNames(fragmentSpreadB.Definition.SelectionSet)
// (F) First, collect all conflicts between these two collections of fields
// (not including any nested fragments).
m.collectConflictsBetween(conflicts, areMutuallyExclusive, fieldsMapA, fieldsMapB)
// (G) Then collect conflicts between the first fragment and any nested
// fragments spread in the second fragment.
for _, fragmentSpread := range fragmentSpreadsB {
check(fragmentSpreadA, fragmentSpread)
}
// (G) Then collect conflicts between the second fragment and any nested
// fragments spread in the first fragment.
for _, fragmentSpread := range fragmentSpreadsA {
check(fragmentSpread, fragmentSpreadB)
}
}
check(fragmentSpreadA, fragmentSpreadB)
}
func (m *overlappingFieldsCanBeMergedManager) findConflictsBetweenSubSelectionSets(areMutuallyExclusive bool, selectionSetA ast.SelectionSet, selectionSetB ast.SelectionSet) *conflictMessageContainer {
var conflicts conflictMessageContainer
fieldsMapA, fragmentSpreadsA := getFieldsAndFragmentNames(selectionSetA)
fieldsMapB, fragmentSpreadsB := getFieldsAndFragmentNames(selectionSetB)
// (H) First, collect all conflicts between these two collections of field.
m.collectConflictsBetween(&conflicts, areMutuallyExclusive, fieldsMapA, fieldsMapB)
// (I) Then collect conflicts between the first collection of fields and
// those referenced by each fragment name associated with the second.
for _, fragmentSpread := range fragmentSpreadsB {
m.comparedFragments = make(map[string]bool)
m.collectConflictsBetweenFieldsAndFragment(&conflicts, areMutuallyExclusive, fieldsMapA, fragmentSpread)
}
// (I) Then collect conflicts between the second collection of fields and
// those referenced by each fragment name associated with the first.
for _, fragmentSpread := range fragmentSpreadsA {
m.comparedFragments = make(map[string]bool)
m.collectConflictsBetweenFieldsAndFragment(&conflicts, areMutuallyExclusive, fieldsMapB, fragmentSpread)
}
// (J) Also collect conflicts between any fragment names by the first and
// fragment names by the second. This compares each item in the first set of
// names to each item in the second set of names.
for _, fragmentSpreadA := range fragmentSpreadsA {
for _, fragmentSpreadB := range fragmentSpreadsB {
m.collectConflictsBetweenFragments(&conflicts, areMutuallyExclusive, fragmentSpreadA, fragmentSpreadB)
}
}
if len(conflicts.Conflicts) == 0 {
return nil
}
return &conflicts
}
func (m *overlappingFieldsCanBeMergedManager) collectConflictsWithin(conflicts *conflictMessageContainer, fieldsMap *sequentialFieldsMap) {
for _, fields := range fieldsMap.Iterator() {
for idx, fieldA := range fields {
for _, fieldB := range fields[idx+1:] {
conflict := m.findConflict(false, fieldA, fieldB)
if conflict != nil {
conflicts.Conflicts = append(conflicts.Conflicts, conflict)
}
}
}
}
}
func (m *overlappingFieldsCanBeMergedManager) collectConflictsBetween(conflicts *conflictMessageContainer, parentFieldsAreMutuallyExclusive bool, fieldsMapA *sequentialFieldsMap, fieldsMapB *sequentialFieldsMap) {
for _, fieldsEntryA := range fieldsMapA.KeyValueIterator() {
fieldsB, ok := fieldsMapB.Get(fieldsEntryA.ResponseName)
if !ok {
continue
}
for _, fieldA := range fieldsEntryA.Fields {
for _, fieldB := range fieldsB {
conflict := m.findConflict(parentFieldsAreMutuallyExclusive, fieldA, fieldB)
if conflict != nil {
conflicts.Conflicts = append(conflicts.Conflicts, conflict)
}
}
}
}
}
func (m *overlappingFieldsCanBeMergedManager) findConflict(parentFieldsAreMutuallyExclusive bool, fieldA *ast.Field, fieldB *ast.Field) *ConflictMessage {
if fieldA.ObjectDefinition == nil || fieldB.ObjectDefinition == nil {
return nil
}
areMutuallyExclusive := parentFieldsAreMutuallyExclusive
if !areMutuallyExclusive {
tmp := fieldA.ObjectDefinition.Name != fieldB.ObjectDefinition.Name
tmp = tmp && fieldA.ObjectDefinition.Kind == ast.Object
tmp = tmp && fieldB.ObjectDefinition.Kind == ast.Object
tmp = tmp && fieldA.Definition != nil && fieldB.Definition != nil
areMutuallyExclusive = tmp
}
fieldNameA := fieldA.Name
if fieldA.Alias != "" {
fieldNameA = fieldA.Alias
}
if !areMutuallyExclusive {
// Two aliases must refer to the same field.
if fieldA.Name != fieldB.Name {
return &ConflictMessage{
ResponseName: fieldNameA,
Message: fmt.Sprintf(`"%s" and "%s" are different fields`, fieldA.Name, fieldB.Name),
Position: fieldB.Position,
}
}
// Two field calls must have the same arguments.
if !sameArguments(fieldA.Arguments, fieldB.Arguments) {
return &ConflictMessage{
ResponseName: fieldNameA,
Message: "they have differing arguments",
Position: fieldB.Position,
}
}
}
if fieldA.Definition != nil && fieldB.Definition != nil && doTypesConflict(m.walker, fieldA.Definition.Type, fieldB.Definition.Type) {
return &ConflictMessage{
ResponseName: fieldNameA,
Message: fmt.Sprintf(`they return conflicting types "%s" and "%s"`, fieldA.Definition.Type.String(), fieldB.Definition.Type.String()),
Position: fieldB.Position,
}
}
// Collect and compare sub-fields. Use the same "visited fragment names" list
// for both collections so fields in a fragment reference are never
// compared to themselves.
conflicts := m.findConflictsBetweenSubSelectionSets(areMutuallyExclusive, fieldA.SelectionSet, fieldB.SelectionSet)
if conflicts == nil {
return nil
}
return &ConflictMessage{
ResponseName: fieldNameA,
SubMessage: conflicts.Conflicts,
Position: fieldB.Position,
}
}
func sameArguments(args1 []*ast.Argument, args2 []*ast.Argument) bool {
if len(args1) != len(args2) {
return false
}
for _, arg1 := range args1 {
var matched bool
for _, arg2 := range args2 {
if arg1.Name == arg2.Name && sameValue(arg1.Value, arg2.Value) {
matched = true
break
}
}
if !matched {
return false
}
}
return true
}
func sameValue(value1 *ast.Value, value2 *ast.Value) bool {
if value1.Kind != value2.Kind {
return false
}
if value1.Raw != value2.Raw {
return false
}
return true
}
func doTypesConflict(walker *Walker, type1 *ast.Type, type2 *ast.Type) bool {
if type1.Elem != nil {
if type2.Elem != nil {
return doTypesConflict(walker, type1.Elem, type2.Elem)
}
return true
}
if type2.Elem != nil {
return true
}
if type1.NonNull && !type2.NonNull {
return true
}
if !type1.NonNull && type2.NonNull {
return true
}
t1 := walker.Schema.Types[type1.NamedType]
t2 := walker.Schema.Types[type2.NamedType]
if (t1.Kind == ast.Scalar || t1.Kind == ast.Enum) && (t2.Kind == ast.Scalar || t2.Kind == ast.Enum) {
return t1.Name != t2.Name
}
return false
}
func getFieldsAndFragmentNames(selectionSet ast.SelectionSet) (*sequentialFieldsMap, []*ast.FragmentSpread) {
fieldsMap := sequentialFieldsMap{
data: make(map[string][]*ast.Field),
}
var fragmentSpreads []*ast.FragmentSpread
var walk func(selectionSet ast.SelectionSet)
walk = func(selectionSet ast.SelectionSet) {
for _, selection := range selectionSet {
switch selection := selection.(type) {
case *ast.Field:
responseName := selection.Name
if selection.Alias != "" {
responseName = selection.Alias
}
fieldsMap.Push(responseName, selection)
case *ast.InlineFragment:
walk(selection.SelectionSet)
case *ast.FragmentSpread:
fragmentSpreads = append(fragmentSpreads, selection)
}
}
}
walk(selectionSet)
return &fieldsMap, fragmentSpreads
}
@@ -1,70 +0,0 @@
package validator
import (
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("PossibleFragmentSpreads", func(observers *Events, addError AddErrFunc) {
validate := func(walker *Walker, parentDef *ast.Definition, fragmentName string, emitError func()) {
if parentDef == nil {
return
}
var parentDefs []*ast.Definition
switch parentDef.Kind {
case ast.Object:
parentDefs = []*ast.Definition{parentDef}
case ast.Interface, ast.Union:
parentDefs = walker.Schema.GetPossibleTypes(parentDef)
default:
return
}
fragmentDefType := walker.Schema.Types[fragmentName]
if fragmentDefType == nil {
return
}
if !fragmentDefType.IsCompositeType() {
// checked by FragmentsOnCompositeTypes
return
}
fragmentDefs := walker.Schema.GetPossibleTypes(fragmentDefType)
for _, fragmentDef := range fragmentDefs {
for _, parentDef := range parentDefs {
if parentDef.Name == fragmentDef.Name {
return
}
}
}
emitError()
}
observers.OnInlineFragment(func(walker *Walker, inlineFragment *ast.InlineFragment) {
validate(walker, inlineFragment.ObjectDefinition, inlineFragment.TypeCondition, func() {
addError(
Message(`Fragment cannot be spread here as objects of type "%s" can never be of type "%s".`, inlineFragment.ObjectDefinition.Name, inlineFragment.TypeCondition),
At(inlineFragment.Position),
)
})
})
observers.OnFragmentSpread(func(walker *Walker, fragmentSpread *ast.FragmentSpread) {
if fragmentSpread.Definition == nil {
return
}
validate(walker, fragmentSpread.ObjectDefinition, fragmentSpread.Definition.TypeCondition, func() {
addError(
Message(`Fragment "%s" cannot be spread here as objects of type "%s" can never be of type "%s".`, fragmentSpread.Name, fragmentSpread.ObjectDefinition.Name, fragmentSpread.Definition.TypeCondition),
At(fragmentSpread.Position),
)
})
})
})
}
@@ -1,64 +0,0 @@
package validator
import (
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("ProvidedRequiredArguments", func(observers *Events, addError AddErrFunc) {
observers.OnField(func(_ *Walker, field *ast.Field) {
if field.Definition == nil {
return
}
argDef:
for _, argDef := range field.Definition.Arguments {
if !argDef.Type.NonNull {
continue
}
if argDef.DefaultValue != nil {
continue
}
for _, arg := range field.Arguments {
if arg.Name == argDef.Name {
continue argDef
}
}
addError(
Message(`Field "%s" argument "%s" of type "%s" is required, but it was not provided.`, field.Name, argDef.Name, argDef.Type.String()),
At(field.Position),
)
}
})
observers.OnDirective(func(_ *Walker, directive *ast.Directive) {
if directive.Definition == nil {
return
}
argDef:
for _, argDef := range directive.Definition.Arguments {
if !argDef.Type.NonNull {
continue
}
if argDef.DefaultValue != nil {
continue
}
for _, arg := range directive.Arguments {
if arg.Name == argDef.Name {
continue argDef
}
}
addError(
Message(`Directive "@%s" argument "%s" of type "%s" is required, but it was not provided.`, directive.Definition.Name, argDef.Name, argDef.Type.String()),
At(directive.Position),
)
}
})
})
}
@@ -1,38 +0,0 @@
package validator
import (
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("ScalarLeafs", func(observers *Events, addError AddErrFunc) {
observers.OnField(func(walker *Walker, field *ast.Field) {
if field.Definition == nil {
return
}
fieldType := walker.Schema.Types[field.Definition.Type.Name()]
if fieldType == nil {
return
}
if fieldType.IsLeafType() && len(field.SelectionSet) > 0 {
addError(
Message(`Field "%s" must not have a selection since type "%s" has no subfields.`, field.Name, fieldType.Name),
At(field.Position),
)
}
if !fieldType.IsLeafType() && len(field.SelectionSet) == 0 {
addError(
Message(`Field "%s" of type "%s" must have a selection of subfields.`, field.Name, field.Definition.Type.String()),
Suggestf(`"%s { ... }"`, field.Name),
At(field.Position),
)
}
})
})
}
@@ -1,88 +0,0 @@
package validator
import (
"strconv"
"strings"
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("SingleFieldSubscriptions", func(observers *Events, addError AddErrFunc) {
observers.OnOperation(func(walker *Walker, operation *ast.OperationDefinition) {
if walker.Schema.Subscription == nil || operation.Operation != ast.Subscription {
return
}
fields := retrieveTopFieldNames(operation.SelectionSet)
name := "Anonymous Subscription"
if operation.Name != "" {
name = `Subscription ` + strconv.Quote(operation.Name)
}
if len(fields) > 1 {
addError(
Message(`%s must select only one top level field.`, name),
At(fields[1].position),
)
}
for _, field := range fields {
if strings.HasPrefix(field.name, "__") {
addError(
Message(`%s must not select an introspection top level field.`, name),
At(field.position),
)
}
}
})
})
}
type topField struct {
name string
position *ast.Position
}
func retrieveTopFieldNames(selectionSet ast.SelectionSet) []*topField {
fields := []*topField{}
inFragmentRecursive := map[string]bool{}
var walk func(selectionSet ast.SelectionSet)
walk = func(selectionSet ast.SelectionSet) {
for _, selection := range selectionSet {
switch selection := selection.(type) {
case *ast.Field:
fields = append(fields, &topField{
name: selection.Name,
position: selection.GetPosition(),
})
case *ast.InlineFragment:
walk(selection.SelectionSet)
case *ast.FragmentSpread:
if selection.Definition == nil {
return
}
fragment := selection.Definition.Name
if !inFragmentRecursive[fragment] {
inFragmentRecursive[fragment] = true
walk(selection.Definition.SelectionSet)
}
}
}
}
walk(selectionSet)
seen := make(map[string]bool, len(fields))
uniquedFields := make([]*topField, 0, len(fields))
for _, field := range fields {
if !seen[field.name] {
uniquedFields = append(uniquedFields, field)
}
seen[field.name] = true
}
return uniquedFields
}
@@ -1,35 +0,0 @@
package validator
import (
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("UniqueArgumentNames", func(observers *Events, addError AddErrFunc) {
observers.OnField(func(_ *Walker, field *ast.Field) {
checkUniqueArgs(field.Arguments, addError)
})
observers.OnDirective(func(_ *Walker, directive *ast.Directive) {
checkUniqueArgs(directive.Arguments, addError)
})
})
}
func checkUniqueArgs(args ast.ArgumentList, addError AddErrFunc) {
knownArgNames := map[string]int{}
for _, arg := range args {
if knownArgNames[arg.Name] == 1 {
addError(
Message(`There can be only one argument named "%s".`, arg.Name),
At(arg.Position),
)
}
knownArgNames[arg.Name]++
}
}
@@ -1,26 +0,0 @@
package validator
import (
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("UniqueDirectivesPerLocation", func(observers *Events, addError AddErrFunc) {
observers.OnDirectiveList(func(_ *Walker, directives []*ast.Directive) {
seen := map[string]bool{}
for _, dir := range directives {
if dir.Name != "repeatable" && seen[dir.Name] {
addError(
Message(`The directive "@%s" can only be used once at this location.`, dir.Name),
At(dir.Position),
)
}
seen[dir.Name] = true
}
})
})
}
@@ -1,24 +0,0 @@
package validator
import (
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("UniqueFragmentNames", func(observers *Events, addError AddErrFunc) {
seenFragments := map[string]bool{}
observers.OnFragment(func(_ *Walker, fragment *ast.FragmentDefinition) {
if seenFragments[fragment.Name] {
addError(
Message(`There can be only one fragment named "%s".`, fragment.Name),
At(fragment.Position),
)
}
seenFragments[fragment.Name] = true
})
})
}
@@ -1,29 +0,0 @@
package validator
import (
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("UniqueInputFieldNames", func(observers *Events, addError AddErrFunc) {
observers.OnValue(func(_ *Walker, value *ast.Value) {
if value.Kind != ast.ObjectValue {
return
}
seen := map[string]bool{}
for _, field := range value.Children {
if seen[field.Name] {
addError(
Message(`There can be only one input field named "%s".`, field.Name),
At(field.Position),
)
}
seen[field.Name] = true
}
})
})
}
@@ -1,24 +0,0 @@
package validator
import (
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("UniqueOperationNames", func(observers *Events, addError AddErrFunc) {
seen := map[string]bool{}
observers.OnOperation(func(_ *Walker, operation *ast.OperationDefinition) {
if seen[operation.Name] {
addError(
Message(`There can be only one operation named "%s".`, operation.Name),
At(operation.Position),
)
}
seen[operation.Name] = true
})
})
}
@@ -1,26 +0,0 @@
package validator
import (
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("UniqueVariableNames", func(observers *Events, addError AddErrFunc) {
observers.OnOperation(func(_ *Walker, operation *ast.OperationDefinition) {
seen := map[string]int{}
for _, def := range operation.VariableDefinitions {
// add the same error only once per a variable.
if seen[def.Variable] == 1 {
addError(
Message(`There can be only one variable named "$%s".`, def.Variable),
At(def.Position),
)
}
seen[def.Variable]++
}
})
})
}
@@ -1,168 +0,0 @@
package validator
import (
"errors"
"fmt"
"strconv"
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("ValuesOfCorrectType", func(observers *Events, addError AddErrFunc) {
observers.OnValue(func(_ *Walker, value *ast.Value) {
if value.Definition == nil || value.ExpectedType == nil {
return
}
if value.Kind == ast.NullValue && value.ExpectedType.NonNull {
addError(
Message(`Expected value of type "%s", found %s.`, value.ExpectedType.String(), value.String()),
At(value.Position),
)
}
if value.Definition.Kind == ast.Scalar {
// Skip custom validating scalars
if !value.Definition.OneOf("Int", "Float", "String", "Boolean", "ID") {
return
}
}
var possibleEnums []string
if value.Definition.Kind == ast.Enum {
for _, val := range value.Definition.EnumValues {
possibleEnums = append(possibleEnums, val.Name)
}
}
rawVal, err := value.Value(nil)
if err != nil {
unexpectedTypeMessage(addError, value)
}
switch value.Kind {
case ast.NullValue:
return
case ast.ListValue:
if value.ExpectedType.Elem == nil {
unexpectedTypeMessage(addError, value)
return
}
case ast.IntValue:
if !value.Definition.OneOf("Int", "Float", "ID") {
unexpectedTypeMessage(addError, value)
}
case ast.FloatValue:
if !value.Definition.OneOf("Float") {
unexpectedTypeMessage(addError, value)
}
case ast.StringValue, ast.BlockValue:
if value.Definition.Kind == ast.Enum {
rawValStr := fmt.Sprint(rawVal)
addError(
Message(`Enum "%s" cannot represent non-enum value: %s.`, value.ExpectedType.String(), value.String()),
SuggestListQuoted("Did you mean the enum value", rawValStr, possibleEnums),
At(value.Position),
)
} else if !value.Definition.OneOf("String", "ID") {
unexpectedTypeMessage(addError, value)
}
case ast.EnumValue:
if value.Definition.Kind != ast.Enum {
rawValStr := fmt.Sprint(rawVal)
addError(
unexpectedTypeMessageOnly(value),
SuggestListUnquoted("Did you mean the enum value", rawValStr, possibleEnums),
At(value.Position),
)
} else if value.Definition.EnumValues.ForName(value.Raw) == nil {
rawValStr := fmt.Sprint(rawVal)
addError(
Message(`Value "%s" does not exist in "%s" enum.`, value.String(), value.ExpectedType.String()),
SuggestListQuoted("Did you mean the enum value", rawValStr, possibleEnums),
At(value.Position),
)
}
case ast.BooleanValue:
if !value.Definition.OneOf("Boolean") {
unexpectedTypeMessage(addError, value)
}
case ast.ObjectValue:
for _, field := range value.Definition.Fields {
if field.Type.NonNull {
fieldValue := value.Children.ForName(field.Name)
if fieldValue == nil && field.DefaultValue == nil {
addError(
Message(`Field "%s.%s" of required type "%s" was not provided.`, value.Definition.Name, field.Name, field.Type.String()),
At(value.Position),
)
continue
}
}
}
for _, fieldValue := range value.Children {
if value.Definition.Fields.ForName(fieldValue.Name) == nil {
var suggestions []string
for _, fieldValue := range value.Definition.Fields {
suggestions = append(suggestions, fieldValue.Name)
}
addError(
Message(`Field "%s" is not defined by type "%s".`, fieldValue.Name, value.Definition.Name),
SuggestListQuoted("Did you mean", fieldValue.Name, suggestions),
At(fieldValue.Position),
)
}
}
case ast.Variable:
return
default:
panic(fmt.Errorf("unhandled %T", value))
}
})
})
}
func unexpectedTypeMessage(addError AddErrFunc, v *ast.Value) {
addError(
unexpectedTypeMessageOnly(v),
At(v.Position),
)
}
func unexpectedTypeMessageOnly(v *ast.Value) ErrorOption {
switch v.ExpectedType.String() {
case "Int", "Int!":
if _, err := strconv.ParseInt(v.Raw, 10, 32); err != nil && errors.Is(err, strconv.ErrRange) {
return Message(`Int cannot represent non 32-bit signed integer value: %s`, v.String())
}
return Message(`Int cannot represent non-integer value: %s`, v.String())
case "String", "String!", "[String]":
return Message(`String cannot represent a non string value: %s`, v.String())
case "Boolean", "Boolean!":
return Message(`Boolean cannot represent a non boolean value: %s`, v.String())
case "Float", "Float!":
return Message(`Float cannot represent non numeric value: %s`, v.String())
case "ID", "ID!":
return Message(`ID cannot represent a non-string and non-integer value: %s`, v.String())
default:
if v.Definition.Kind == ast.Enum {
return Message(`Enum "%s" cannot represent non-enum value: %s.`, v.ExpectedType.String(), v.String())
}
return Message(`Expected value of type "%s", found %s.`, v.ExpectedType.String(), v.String())
}
}
@@ -1,30 +0,0 @@
package validator
import (
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("VariablesAreInputTypes", func(observers *Events, addError AddErrFunc) {
observers.OnOperation(func(_ *Walker, operation *ast.OperationDefinition) {
for _, def := range operation.VariableDefinitions {
if def.Definition == nil {
continue
}
if !def.Definition.IsInputType() {
addError(
Message(
`Variable "$%s" cannot be non-input type "%s".`,
def.Variable,
def.Type.String(),
),
At(def.Position),
)
}
}
})
})
}
@@ -1,40 +0,0 @@
package validator
import (
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
//nolint:revive // Validator rules each use dot imports for convenience.
. "github.com/open-policy-agent/opa/internal/gqlparser/validator"
)
func init() {
AddRule("VariablesInAllowedPosition", func(observers *Events, addError AddErrFunc) {
observers.OnValue(func(walker *Walker, value *ast.Value) {
if value.Kind != ast.Variable || value.ExpectedType == nil || value.VariableDefinition == nil || walker.CurrentOperation == nil {
return
}
tmp := *value.ExpectedType
// todo: move me into walk
// If there is a default non nullable types can be null
if value.VariableDefinition.DefaultValue != nil && value.VariableDefinition.DefaultValue.Kind != ast.NullValue {
if value.ExpectedType.NonNull {
tmp.NonNull = false
}
}
if !value.VariableDefinition.Type.IsCompatible(&tmp) {
addError(
Message(
`Variable "%s" of type "%s" used in position expecting type "%s".`,
value,
value.VariableDefinition.Type.String(),
value.ExpectedType.String(),
),
At(value.Position),
)
}
})
})
}
@@ -1,513 +0,0 @@
package validator
import (
"sort"
"strconv"
"strings"
//nolint:revive
. "github.com/open-policy-agent/opa/internal/gqlparser/ast"
"github.com/open-policy-agent/opa/internal/gqlparser/gqlerror"
"github.com/open-policy-agent/opa/internal/gqlparser/parser"
)
func LoadSchema(inputs ...*Source) (*Schema, error) {
ast, err := parser.ParseSchemas(inputs...)
if err != nil {
return nil, err
}
return ValidateSchemaDocument(ast)
}
func ValidateSchemaDocument(ast *SchemaDocument) (*Schema, error) {
schema := Schema{
Types: map[string]*Definition{},
Directives: map[string]*DirectiveDefinition{},
PossibleTypes: map[string][]*Definition{},
Implements: map[string][]*Definition{},
}
for i, def := range ast.Definitions {
if schema.Types[def.Name] != nil {
return nil, gqlerror.ErrorPosf(def.Position, "Cannot redeclare type %s.", def.Name)
}
schema.Types[def.Name] = ast.Definitions[i]
}
defs := append(DefinitionList{}, ast.Definitions...)
for _, ext := range ast.Extensions {
def := schema.Types[ext.Name]
if def == nil {
schema.Types[ext.Name] = &Definition{
Kind: ext.Kind,
Name: ext.Name,
Position: ext.Position,
}
def = schema.Types[ext.Name]
defs = append(defs, def)
}
if def.Kind != ext.Kind {
return nil, gqlerror.ErrorPosf(ext.Position, "Cannot extend type %s because the base type is a %s, not %s.", ext.Name, def.Kind, ext.Kind)
}
def.Directives = append(def.Directives, ext.Directives...)
def.Interfaces = append(def.Interfaces, ext.Interfaces...)
def.Fields = append(def.Fields, ext.Fields...)
def.Types = append(def.Types, ext.Types...)
def.EnumValues = append(def.EnumValues, ext.EnumValues...)
}
for _, def := range defs {
switch def.Kind {
case Union:
for _, t := range def.Types {
schema.AddPossibleType(def.Name, schema.Types[t])
schema.AddImplements(t, def)
}
case InputObject, Object:
for _, intf := range def.Interfaces {
schema.AddPossibleType(intf, def)
schema.AddImplements(def.Name, schema.Types[intf])
}
schema.AddPossibleType(def.Name, def)
case Interface:
for _, intf := range def.Interfaces {
schema.AddPossibleType(intf, def)
schema.AddImplements(def.Name, schema.Types[intf])
}
}
}
for i, dir := range ast.Directives {
if schema.Directives[dir.Name] != nil {
// While the spec says SDL must not (§3.5) explicitly define builtin
// scalars, it may (§3.13) define builtin directives. Here we check for
// that, and reject doubly-defined directives otherwise.
switch dir.Name {
case "include", "skip", "deprecated", "specifiedBy": // the builtins
// In principle here we might want to validate that the
// directives are the same. But they might not be, if the
// server has an older spec than we do. (Plus, validating this
// is a lot of work.) So we just keep the first one we saw.
// That's an arbitrary choice, but in theory the only way it
// fails is if the server is using features newer than this
// version of gqlparser, in which case they're in trouble
// anyway.
default:
return nil, gqlerror.ErrorPosf(dir.Position, "Cannot redeclare directive %s.", dir.Name)
}
}
schema.Directives[dir.Name] = ast.Directives[i]
}
if len(ast.Schema) > 1 {
return nil, gqlerror.ErrorPosf(ast.Schema[1].Position, "Cannot have multiple schema entry points, consider schema extensions instead.")
}
if len(ast.Schema) == 1 {
schema.Description = ast.Schema[0].Description
for _, entrypoint := range ast.Schema[0].OperationTypes {
def := schema.Types[entrypoint.Type]
if def == nil {
return nil, gqlerror.ErrorPosf(entrypoint.Position, "Schema root %s refers to a type %s that does not exist.", entrypoint.Operation, entrypoint.Type)
}
switch entrypoint.Operation {
case Query:
schema.Query = def
case Mutation:
schema.Mutation = def
case Subscription:
schema.Subscription = def
}
}
}
for _, ext := range ast.SchemaExtension {
for _, entrypoint := range ext.OperationTypes {
def := schema.Types[entrypoint.Type]
if def == nil {
return nil, gqlerror.ErrorPosf(entrypoint.Position, "Schema root %s refers to a type %s that does not exist.", entrypoint.Operation, entrypoint.Type)
}
switch entrypoint.Operation {
case Query:
schema.Query = def
case Mutation:
schema.Mutation = def
case Subscription:
schema.Subscription = def
}
}
}
if err := validateTypeDefinitions(&schema); err != nil {
return nil, err
}
if err := validateDirectiveDefinitions(&schema); err != nil {
return nil, err
}
// Inferred root operation type names should be performed only when a `schema` directive is
// **not** provided, when it is, `Mutation` and `Subscription` becomes valid types and are not
// assigned as a root operation on the schema.
if len(ast.Schema) == 0 {
if schema.Query == nil && schema.Types["Query"] != nil {
schema.Query = schema.Types["Query"]
}
if schema.Mutation == nil && schema.Types["Mutation"] != nil {
schema.Mutation = schema.Types["Mutation"]
}
if schema.Subscription == nil && schema.Types["Subscription"] != nil {
schema.Subscription = schema.Types["Subscription"]
}
}
if schema.Query != nil {
schema.Query.Fields = append(
schema.Query.Fields,
&FieldDefinition{
Name: "__schema",
Type: NonNullNamedType("__Schema", nil),
},
&FieldDefinition{
Name: "__type",
Type: NamedType("__Type", nil),
Arguments: ArgumentDefinitionList{
{Name: "name", Type: NonNullNamedType("String", nil)},
},
},
)
}
return &schema, nil
}
func validateTypeDefinitions(schema *Schema) *gqlerror.Error {
types := make([]string, 0, len(schema.Types))
for typ := range schema.Types {
types = append(types, typ)
}
sort.Strings(types)
for _, typ := range types {
err := validateDefinition(schema, schema.Types[typ])
if err != nil {
return err
}
}
return nil
}
func validateDirectiveDefinitions(schema *Schema) *gqlerror.Error {
directives := make([]string, 0, len(schema.Directives))
for directive := range schema.Directives {
directives = append(directives, directive)
}
sort.Strings(directives)
for _, directive := range directives {
err := validateDirective(schema, schema.Directives[directive])
if err != nil {
return err
}
}
return nil
}
func validateDirective(schema *Schema, def *DirectiveDefinition) *gqlerror.Error {
if err := validateName(def.Position, def.Name); err != nil {
// now, GraphQL spec doesn't have reserved directive name
return err
}
return validateArgs(schema, def.Arguments, def)
}
func validateDefinition(schema *Schema, def *Definition) *gqlerror.Error {
for _, field := range def.Fields {
if err := validateName(field.Position, field.Name); err != nil {
// now, GraphQL spec doesn't have reserved field name
return err
}
if err := validateTypeRef(schema, field.Type); err != nil {
return err
}
if err := validateArgs(schema, field.Arguments, nil); err != nil {
return err
}
wantDirLocation := LocationFieldDefinition
if def.Kind == InputObject {
wantDirLocation = LocationInputFieldDefinition
}
if err := validateDirectives(schema, field.Directives, wantDirLocation, nil); err != nil {
return err
}
}
for _, typ := range def.Types {
typDef := schema.Types[typ]
if typDef == nil {
return gqlerror.ErrorPosf(def.Position, "Undefined type %s.", strconv.Quote(typ))
}
if !isValidKind(typDef.Kind, Object) {
return gqlerror.ErrorPosf(def.Position, "%s type %s must be %s.", def.Kind, strconv.Quote(typ), kindList(Object))
}
}
for _, intf := range def.Interfaces {
if err := validateImplements(schema, def, intf); err != nil {
return err
}
}
switch def.Kind {
case Object, Interface:
if len(def.Fields) == 0 {
return gqlerror.ErrorPosf(def.Position, "%s %s: must define one or more fields.", def.Kind, def.Name)
}
for _, field := range def.Fields {
if typ, ok := schema.Types[field.Type.Name()]; ok {
if !isValidKind(typ.Kind, Scalar, Object, Interface, Union, Enum) {
return gqlerror.ErrorPosf(field.Position, "%s %s: field must be one of %s.", def.Kind, def.Name, kindList(Scalar, Object, Interface, Union, Enum))
}
}
}
case Enum:
if len(def.EnumValues) == 0 {
return gqlerror.ErrorPosf(def.Position, "%s %s: must define one or more unique enum values.", def.Kind, def.Name)
}
for _, value := range def.EnumValues {
for _, nonEnum := range [3]string{"true", "false", "null"} {
if value.Name == nonEnum {
return gqlerror.ErrorPosf(def.Position, "%s %s: non-enum value %s.", def.Kind, def.Name, value.Name)
}
}
}
case InputObject:
if len(def.Fields) == 0 {
return gqlerror.ErrorPosf(def.Position, "%s %s: must define one or more input fields.", def.Kind, def.Name)
}
for _, field := range def.Fields {
if typ, ok := schema.Types[field.Type.Name()]; ok {
if !isValidKind(typ.Kind, Scalar, Enum, InputObject) {
return gqlerror.ErrorPosf(field.Position, "%s %s: field must be one of %s.", typ.Kind, field.Name, kindList(Scalar, Enum, InputObject))
}
}
}
}
for idx, field1 := range def.Fields {
for _, field2 := range def.Fields[idx+1:] {
if field1.Name == field2.Name {
return gqlerror.ErrorPosf(field2.Position, "Field %s.%s can only be defined once.", def.Name, field2.Name)
}
}
}
if !def.BuiltIn {
// GraphQL spec has reserved type names a lot!
err := validateName(def.Position, def.Name)
if err != nil {
return err
}
}
return validateDirectives(schema, def.Directives, DirectiveLocation(def.Kind), nil)
}
func validateTypeRef(schema *Schema, typ *Type) *gqlerror.Error {
if schema.Types[typ.Name()] == nil {
return gqlerror.ErrorPosf(typ.Position, "Undefined type %s.", typ.Name())
}
return nil
}
func validateArgs(schema *Schema, args ArgumentDefinitionList, currentDirective *DirectiveDefinition) *gqlerror.Error {
for _, arg := range args {
if err := validateName(arg.Position, arg.Name); err != nil {
// now, GraphQL spec doesn't have reserved argument name
return err
}
if err := validateTypeRef(schema, arg.Type); err != nil {
return err
}
def := schema.Types[arg.Type.Name()]
if !def.IsInputType() {
return gqlerror.ErrorPosf(
arg.Position,
"cannot use %s as argument %s because %s is not a valid input type",
arg.Type.String(),
arg.Name,
def.Kind,
)
}
if err := validateDirectives(schema, arg.Directives, LocationArgumentDefinition, currentDirective); err != nil {
return err
}
}
return nil
}
func validateDirectives(schema *Schema, dirs DirectiveList, location DirectiveLocation, currentDirective *DirectiveDefinition) *gqlerror.Error {
for _, dir := range dirs {
if err := validateName(dir.Position, dir.Name); err != nil {
// now, GraphQL spec doesn't have reserved directive name
return err
}
if currentDirective != nil && dir.Name == currentDirective.Name {
return gqlerror.ErrorPosf(dir.Position, "Directive %s cannot refer to itself.", currentDirective.Name)
}
if schema.Directives[dir.Name] == nil {
return gqlerror.ErrorPosf(dir.Position, "Undefined directive %s.", dir.Name)
}
validKind := false
for _, dirLocation := range schema.Directives[dir.Name].Locations {
if dirLocation == location {
validKind = true
break
}
}
if !validKind {
return gqlerror.ErrorPosf(dir.Position, "Directive %s is not applicable on %s.", dir.Name, location)
}
dir.Definition = schema.Directives[dir.Name]
}
return nil
}
func validateImplements(schema *Schema, def *Definition, intfName string) *gqlerror.Error {
// see validation rules at the bottom of
// https://facebook.github.io/graphql/October2021/#sec-Objects
intf := schema.Types[intfName]
if intf == nil {
return gqlerror.ErrorPosf(def.Position, "Undefined type %s.", strconv.Quote(intfName))
}
if intf.Kind != Interface {
return gqlerror.ErrorPosf(def.Position, "%s is a non interface type %s.", strconv.Quote(intfName), intf.Kind)
}
for _, requiredField := range intf.Fields {
foundField := def.Fields.ForName(requiredField.Name)
if foundField == nil {
return gqlerror.ErrorPosf(def.Position,
`For %s to implement %s it must have a field called %s.`,
def.Name, intf.Name, requiredField.Name,
)
}
if !isCovariant(schema, requiredField.Type, foundField.Type) {
return gqlerror.ErrorPosf(foundField.Position,
`For %s to implement %s the field %s must have type %s.`,
def.Name, intf.Name, requiredField.Name, requiredField.Type.String(),
)
}
for _, requiredArg := range requiredField.Arguments {
foundArg := foundField.Arguments.ForName(requiredArg.Name)
if foundArg == nil {
return gqlerror.ErrorPosf(foundField.Position,
`For %s to implement %s the field %s must have the same arguments but it is missing %s.`,
def.Name, intf.Name, requiredField.Name, requiredArg.Name,
)
}
if !requiredArg.Type.IsCompatible(foundArg.Type) {
return gqlerror.ErrorPosf(foundArg.Position,
`For %s to implement %s the field %s must have the same arguments but %s has the wrong type.`,
def.Name, intf.Name, requiredField.Name, requiredArg.Name,
)
}
}
for _, foundArgs := range foundField.Arguments {
if requiredField.Arguments.ForName(foundArgs.Name) == nil && foundArgs.Type.NonNull && foundArgs.DefaultValue == nil {
return gqlerror.ErrorPosf(foundArgs.Position,
`For %s to implement %s any additional arguments on %s must be optional or have a default value but %s is required.`,
def.Name, intf.Name, foundField.Name, foundArgs.Name,
)
}
}
}
return validateTypeImplementsAncestors(schema, def, intfName)
}
// validateTypeImplementsAncestors
// https://github.com/graphql/graphql-js/blob/47bd8c8897c72d3efc17ecb1599a95cee6bac5e8/src/type/validate.ts#L428
func validateTypeImplementsAncestors(schema *Schema, def *Definition, intfName string) *gqlerror.Error {
intf := schema.Types[intfName]
if intf == nil {
return gqlerror.ErrorPosf(def.Position, "Undefined type %s.", strconv.Quote(intfName))
}
for _, transitive := range intf.Interfaces {
if !containsString(def.Interfaces, transitive) {
if transitive == def.Name {
return gqlerror.ErrorPosf(def.Position,
`Type %s cannot implement %s because it would create a circular reference.`,
def.Name, intfName,
)
}
return gqlerror.ErrorPosf(def.Position,
`Type %s must implement %s because it is implemented by %s.`,
def.Name, transitive, intfName,
)
}
}
return nil
}
func containsString(slice []string, want string) bool {
for _, str := range slice {
if want == str {
return true
}
}
return false
}
func isCovariant(schema *Schema, required *Type, actual *Type) bool {
if required.NonNull && !actual.NonNull {
return false
}
if required.NamedType != "" {
if required.NamedType == actual.NamedType {
return true
}
for _, pt := range schema.PossibleTypes[required.NamedType] {
if pt.Name == actual.NamedType {
return true
}
}
return false
}
if required.Elem != nil && actual.Elem == nil {
return false
}
return isCovariant(schema, required.Elem, actual.Elem)
}
func validateName(pos *Position, name string) *gqlerror.Error {
if strings.HasPrefix(name, "__") {
return gqlerror.ErrorPosf(pos, `Name "%s" must not begin with "__", which is reserved by GraphQL introspection.`, name)
}
return nil
}
func isValidKind(kind DefinitionKind, valid ...DefinitionKind) bool {
for _, k := range valid {
if kind == k {
return true
}
}
return false
}
func kindList(kinds ...DefinitionKind) string {
s := make([]string, len(kinds))
for i, k := range kinds {
s[i] = string(k)
}
return strings.Join(s, ", ")
}
@@ -1,678 +0,0 @@
types:
- name: cannot be redeclared
input: |
type A {
name: String
}
type A {
name: String
}
error:
message: "Cannot redeclare type A."
locations: [{line: 4, column: 6}]
- name: cannot be duplicated field at same definition 1
input: |
type A {
name: String
name: String
}
error:
message: "Field A.name can only be defined once."
locations: [{line: 3, column: 3}]
- name: cannot be duplicated field at same definition 2
input: |
type A {
name: String
}
extend type A {
name: String
}
error:
message: "Field A.name can only be defined once."
locations: [{line: 5, column: 3}]
- name: cannot be duplicated field at same definition 3
input: |
type A {
name: String
}
extend type A {
age: Int
age: Int
}
error:
message: "Field A.age can only be defined once."
locations: [{line: 6, column: 3}]
object types:
- name: must define one or more fields
input: |
directive @D on OBJECT
# This pattern rejected by parser
# type InvalidObject1 {}
type InvalidObject2 @D
type ValidObject {
id: ID
}
extend type ValidObject @D
extend type ValidObject {
b: Int
}
error:
message: 'OBJECT InvalidObject2: must define one or more fields.'
locations: [{line: 6, column: 6}]
- name: check reserved names on type name
input: |
type __FooBar {
id: ID
}
error:
message: 'Name "__FooBar" must not begin with "__", which is reserved by GraphQL introspection.'
locations: [{line: 1, column: 6}]
- name: check reserved names on type field
input: |
type FooBar {
__id: ID
}
error:
message: 'Name "__id" must not begin with "__", which is reserved by GraphQL introspection.'
locations: [{line: 2, column: 3}]
- name: check reserved names on type field argument
input: |
type FooBar {
foo(__bar: ID): ID
}
error:
message: 'Name "__bar" must not begin with "__", which is reserved by GraphQL introspection.'
locations: [{line: 2, column: 7}]
- name: must not allow input object as field type
input: |
input Input {
id: ID
}
type Query {
input: Input!
}
error:
message: 'OBJECT Query: field must be one of SCALAR, OBJECT, INTERFACE, UNION, ENUM.'
locations: [{line: 5, column: 3}]
interfaces:
- name: must exist
input: |
type Thing implements Object {
id: ID!
}
type Query {
Things: [Thing!]!
}
error:
message: 'Undefined type "Object".'
locations: [{line: 1, column: 6}]
- name: must be an interface
input: |
type Thing implements Object {
id: ID!
}
type Query {
Things: [Thing!]!
}
type Object {
name: String
}
error:
message: '"Object" is a non interface type OBJECT.'
locations: [{line: 1, column: 6}]
- name: must define one or more fields
input: |
directive @D on INTERFACE
# This pattern rejected by parser
# interface InvalidInterface1 {}
interface InvalidInterface2 @D
interface ValidInterface {
id: ID
}
extend interface ValidInterface @D
extend interface ValidInterface {
b: Int
}
error:
message: 'INTERFACE InvalidInterface2: must define one or more fields.'
locations: [{line: 6, column: 11}]
- name: check reserved names on type name
input: |
interface __FooBar {
id: ID
}
error:
message: 'Name "__FooBar" must not begin with "__", which is reserved by GraphQL introspection.'
locations: [{line: 1, column: 11}]
- name: must not allow input object as field type
input: |
input Input {
id: ID
}
type Query {
foo: Foo!
}
interface Foo {
input: Input!
}
error:
message: 'INTERFACE Foo: field must be one of SCALAR, OBJECT, INTERFACE, UNION, ENUM.'
locations: [{line: 8, column: 3}]
- name: must have all fields from interface
input: |
type Bar implements BarInterface {
someField: Int!
}
interface BarInterface {
id: ID!
}
error:
message: 'For Bar to implement BarInterface it must have a field called id.'
locations: [{line: 1, column: 6}]
- name: must have same type of fields
input: |
type Bar implements BarInterface {
id: Int!
}
interface BarInterface {
id: ID!
}
error:
message: 'For Bar to implement BarInterface the field id must have type ID!.'
locations: [{line: 2, column: 5}]
- name: must have all required arguments
input: |
type Bar implements BarInterface {
id: ID!
}
interface BarInterface {
id(ff: Int!): ID!
}
error:
message: 'For Bar to implement BarInterface the field id must have the same arguments but it is missing ff.'
locations: [{line: 2, column: 5}]
- name: must have same argument types
input: |
type Bar implements BarInterface {
id(ff: ID!): ID!
}
interface BarInterface {
id(ff: Int!): ID!
}
error:
message: 'For Bar to implement BarInterface the field id must have the same arguments but ff has the wrong type.'
locations: [{line: 2, column: 8}]
- name: may defined additional nullable arguments
input: |
type Bar implements BarInterface {
id(opt: Int): ID!
}
interface BarInterface {
id: ID!
}
- name: may defined additional required arguments with defaults
input: |
type Bar implements BarInterface {
id(opt: Int! = 1): ID!
}
interface BarInterface {
id: ID!
}
- name: must not define additional required arguments without defaults
input: |
type Bar implements BarInterface {
id(opt: Int!): ID!
}
interface BarInterface {
id: ID!
}
error:
message: 'For Bar to implement BarInterface any additional arguments on id must be optional or have a default value but opt is required.'
locations: [{line: 2, column: 8}]
- name: can have covariant argument types
input: |
union U = A|B
type A { name: String }
type B { name: String }
type Bar implements BarInterface {
f: A!
}
interface BarInterface {
f: U!
}
- name: may define intermediate interfaces
input: |
interface IA {
id: ID!
}
interface IIA implements IA {
id: ID!
}
type A implements IIA & IA {
id: ID!
}
- name: Type Foo must implement Baz because it is implemented by Bar
input: |
interface Baz {
baz: String
}
interface Bar implements Baz {
bar: String
baz: String
}
type Foo implements Bar {
foo: String
bar: String
baz: String
}
error:
message: 'Type Foo must implement Baz because it is implemented by Bar.'
locations: [{line: 10, column: 6}]
- name: circular reference error
input: |
interface Circular1 implements Circular2 {
id: ID!
}
interface Circular2 implements Circular1 {
id: ID!
}
error:
message: 'Type Circular1 cannot implement Circular2 because it would create a circular reference.'
locations: [{line: 1, column: 11}]
inputs:
- name: must define one or more input fields
input: |
directive @D on INPUT_OBJECT
# This pattern rejected by parser
# input InvalidInput1 {}
input InvalidInput2 @D
input ValidInput {
id: ID
}
extend input ValidInput @D
extend input ValidInput {
b: Int
}
error:
message: 'INPUT_OBJECT InvalidInput2: must define one or more input fields.'
locations: [{line: 6, column: 7}]
- name: check reserved names on type name
input: |
input __FooBar {
id: ID
}
error:
message: 'Name "__FooBar" must not begin with "__", which is reserved by GraphQL introspection.'
locations: [{line: 1, column: 7}]
- name: fields cannot be Objects
input: |
type Object { id: ID }
input Foo { a: Object! }
error:
message: 'OBJECT a: field must be one of SCALAR, ENUM, INPUT_OBJECT.'
locations: [{line: 2, column: 13}]
- name: fields cannot be Interfaces
input: |
interface Interface { id: ID! }
input Foo { a: Interface! }
error:
message: 'INTERFACE a: field must be one of SCALAR, ENUM, INPUT_OBJECT.'
locations: [{line: 2, column: 13}]
- name: fields cannot be Unions
input: |
type Object { id: ID }
union Union = Object
input Foo { a: Union! }
error:
message: 'UNION a: field must be one of SCALAR, ENUM, INPUT_OBJECT.'
locations: [{line: 3, column: 13}]
args:
- name: Valid arg types
input: |
input Input { id: ID }
enum Enum { A }
scalar Scalar
type Query {
f(a: Input, b: Scalar, c: Enum): Boolean!
}
- name: Objects not allowed
input: |
type Object { id: ID }
type Query { f(a: Object): Boolean! }
error:
message: 'cannot use Object as argument a because OBJECT is not a valid input type'
locations: [{line: 2, column: 16}]
- name: Union not allowed
input: |
type Object { id: ID }
union Union = Object
type Query { f(a: Union): Boolean! }
error:
message: 'cannot use Union as argument a because UNION is not a valid input type'
locations: [{line: 3, column: 16}]
- name: Interface not allowed
input: |
interface Interface { id: ID }
type Query { f(a: Interface): Boolean! }
error:
message: 'cannot use Interface as argument a because INTERFACE is not a valid input type'
locations: [{line: 2, column: 16}]
enums:
- name: must define one or more unique enum values
input: |
directive @D on ENUM
# This pattern rejected by parser
# enum InvalidEmum1 {}
enum InvalidEnum2 @D
enum ValidEnum {
FOO
}
extend enum ValidEnum @D
extend enum ValidEnum {
BAR
}
error:
message: 'ENUM InvalidEnum2: must define one or more unique enum values.'
locations: [{line: 6, column: 6}]
- name: check reserved names on type name
input: |
enum __FooBar {
A
B
}
error:
message: 'Name "__FooBar" must not begin with "__", which is reserved by GraphQL introspection.'
locations: [{line: 1, column: 6}]
unions:
- name: union types must be defined
input: |
union Foo = Bar | Baz
type Bar {
id: ID
}
error:
message: "Undefined type \"Baz\"."
locations: [{line: 1, column: 7}]
- name: union types must be objects
input: |
union Foo = Baz
interface Baz {
id: ID
}
error:
message: "UNION type \"Baz\" must be OBJECT."
locations: [{line: 1, column: 7}]
- name: unions of pure type extensions are valid
input: |
type Review {
body: String!
author: User! @provides(fields: "username")
product: Product!
}
extend type User @key(fields: "id") {
id: ID! @external
reviews: [Review]
}
extend type Product @key(fields: "upc") {
upc: String! @external
reviews: [Review]
}
union Foo = User | Product
scalar _Any
scalar _FieldSet
directive @external on FIELD_DEFINITION
directive @requires(fields: _FieldSet!) on FIELD_DEFINITION
directive @provides(fields: _FieldSet!) on FIELD_DEFINITION
directive @key(fields: _FieldSet!) on OBJECT | INTERFACE
directive @extends on OBJECT
type extensions:
- name: can extend non existant types
input: |
extend type A {
name: String
}
- name: cannot extend incorret type existant types
input: |
scalar A
extend type A {
name: String
}
error:
message: "Cannot extend type A because the base type is a SCALAR, not OBJECT."
locations: [{line: 2, column: 13}]
directives:
- name: cannot redeclare directives
input: |
directive @A on FIELD_DEFINITION
directive @A on FIELD_DEFINITION
error:
message: "Cannot redeclare directive A."
locations: [{line: 2, column: 12}]
- name: can redeclare builtin directives
input: |
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
- name: must be declared
input: |
type User {
name: String @foo
}
error:
message: "Undefined directive foo."
locations: [{line: 2, column: 17}]
- name: cannot be self-referential
input: |
directive @A(foo: Int! @A) on FIELD_DEFINITION
error:
message: "Directive A cannot refer to itself."
locations: [{line: 1, column: 25}]
- name: check reserved names on type name
input: |
directive @__A on FIELD_DEFINITION
error:
message: 'Name "__A" must not begin with "__", which is reserved by GraphQL introspection.'
locations: [{line: 1, column: 12}]
- name: Valid arg types
input: |
input Input { id: ID }
enum Enum { A }
scalar Scalar
directive @A(a: Input, b: Scalar, c: Enum) on FIELD_DEFINITION
- name: Objects not allowed
input: |
type Object { id: ID }
directive @A(a: Object) on FIELD_DEFINITION
error:
message: 'cannot use Object as argument a because OBJECT is not a valid input type'
locations: [{line: 2, column: 14}]
- name: Union not allowed
input: |
type Object { id: ID }
union Union = Object
directive @A(a: Union) on FIELD_DEFINITION
error:
message: 'cannot use Union as argument a because UNION is not a valid input type'
locations: [{line: 3, column: 14}]
- name: Interface not allowed
input: |
interface Interface { id: ID }
directive @A(a: Interface) on FIELD_DEFINITION
error:
message: 'cannot use Interface as argument a because INTERFACE is not a valid input type'
locations: [{line: 2, column: 14}]
- name: Invalid location usage not allowed
input: |
directive @test on FIELD_DEFINITION
input I1 @test { f: String }
error:
message: 'Directive test is not applicable on INPUT_OBJECT.'
locations: [{line: 2, column: 11}]
- name: Valid location usage
input: |
directive @testInputField on INPUT_FIELD_DEFINITION
directive @testField on FIELD_DEFINITION
directive @inp on INPUT_OBJECT
input I1 @inp { f: String @testInputField }
type P { name: String @testField }
interface I { id: ID @testField }
entry points:
- name: multiple schema entry points
input: |
schema {
query: Query
}
schema {
query: Query
}
scalar Query
error:
message: "Cannot have multiple schema entry points, consider schema extensions instead."
locations: [{line: 4, column: 8}]
- name: Undefined schema entrypoint
input: |
schema {
query: Query
}
error:
message: "Schema root query refers to a type Query that does not exist."
locations: [{line: 2, column: 3}]
entry point extensions:
- name: Undefined schema entrypoint
input: |
schema {
query: Query
}
scalar Query
extend schema {
mutation: Mutation
}
error:
message: "Schema root mutation refers to a type Mutation that does not exist."
locations: [{line: 6, column: 3}]
type references:
- name: Field types
input: |
type User {
posts: Post
}
error:
message: "Undefined type Post."
locations: [{line: 2, column: 10}]
- name: Arg types
input: |
type User {
posts(foo: FooBar): String
}
error:
message: "Undefined type FooBar."
locations: [{line: 2, column: 14}]
- name: Directive arg types
input: |
directive @Foo(foo: FooBar) on FIELD_DEFINITION
error:
message: "Undefined type FooBar."
locations: [{line: 1, column: 21}]
- name: Invalid enum value
input: |
enum Enum { true }
error:
message: "ENUM Enum: non-enum value true."
locations: [{line: 1, column: 6}]
@@ -1,69 +0,0 @@
package validator
import (
"math"
"sort"
"strings"
"github.com/agnivade/levenshtein"
)
// Given an invalid input string and a list of valid options, returns a filtered
// list of valid options sorted based on their similarity with the input.
func SuggestionList(input string, options []string) []string {
var results []string
optionsByDistance := map[string]int{}
for _, option := range options {
distance := lexicalDistance(input, option)
threshold := calcThreshold(input)
if distance <= threshold {
results = append(results, option)
optionsByDistance[option] = distance
}
}
sort.Slice(results, func(i, j int) bool {
return optionsByDistance[results[i]] < optionsByDistance[results[j]]
})
return results
}
func calcThreshold(a string) (threshold int) {
// the logic is copied from here
// https://github.com/graphql/graphql-js/blob/47bd8c8897c72d3efc17ecb1599a95cee6bac5e8/src/jsutils/suggestionList.ts#L14
threshold = int(math.Floor(float64(len(a))*0.4) + 1)
if threshold < 1 {
threshold = 1
}
return
}
// Computes the lexical distance between strings A and B.
//
// The "distance" between two strings is given by counting the minimum number
// of edits needed to transform string A into string B. An edit can be an
// insertion, deletion, or substitution of a single character, or a swap of two
// adjacent characters.
//
// Includes a custom alteration from Damerau-Levenshtein to treat case changes
// as a single edit which helps identify mis-cased values with an edit distance
// of 1.
//
// This distance can be useful for detecting typos in input or sorting
func lexicalDistance(a, b string) int {
if a == b {
return 0
}
a = strings.ToLower(a)
b = strings.ToLower(b)
// Any case change counts as a single edit
if a == b {
return 1
}
return levenshtein.ComputeDistance(a, b)
}
@@ -1,45 +0,0 @@
package validator
import (
//nolint:revive
. "github.com/open-policy-agent/opa/internal/gqlparser/ast"
"github.com/open-policy-agent/opa/internal/gqlparser/gqlerror"
)
type AddErrFunc func(options ...ErrorOption)
type ruleFunc func(observers *Events, addError AddErrFunc)
type rule struct {
name string
rule ruleFunc
}
var rules []rule
// addRule to rule set.
// f is called once each time `Validate` is executed.
func AddRule(name string, f ruleFunc) {
rules = append(rules, rule{name: name, rule: f})
}
func Validate(schema *Schema, doc *QueryDocument) gqlerror.List {
var errs gqlerror.List
observers := &Events{}
for i := range rules {
rule := rules[i]
rule.rule(observers, func(options ...ErrorOption) {
err := &gqlerror.Error{
Rule: rule.name,
}
for _, o := range options {
o(err)
}
errs = append(errs, err)
})
}
Walk(schema, doc, observers)
return errs
}
@@ -1,259 +0,0 @@
package validator
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
"github.com/open-policy-agent/opa/internal/gqlparser/gqlerror"
)
var ErrUnexpectedType = errors.New("Unexpected Type")
// VariableValues coerces and validates variable values
func VariableValues(schema *ast.Schema, op *ast.OperationDefinition, variables map[string]interface{}) (map[string]interface{}, error) {
coercedVars := map[string]interface{}{}
validator := varValidator{
path: ast.Path{ast.PathName("variable")},
schema: schema,
}
for _, v := range op.VariableDefinitions {
validator.path = append(validator.path, ast.PathName(v.Variable))
if !v.Definition.IsInputType() {
return nil, gqlerror.ErrorPathf(validator.path, "must an input type")
}
val, hasValue := variables[v.Variable]
if !hasValue {
if v.DefaultValue != nil {
var err error
val, err = v.DefaultValue.Value(nil)
if err != nil {
return nil, gqlerror.WrapPath(validator.path, err)
}
hasValue = true
} else if v.Type.NonNull {
return nil, gqlerror.ErrorPathf(validator.path, "must be defined")
}
}
if hasValue {
if val == nil {
if v.Type.NonNull {
return nil, gqlerror.ErrorPathf(validator.path, "cannot be null")
}
coercedVars[v.Variable] = nil
} else {
rv := reflect.ValueOf(val)
jsonNumber, isJSONNumber := val.(json.Number)
if isJSONNumber {
if v.Type.NamedType == "Int" {
n, err := jsonNumber.Int64()
if err != nil {
return nil, gqlerror.ErrorPathf(validator.path, "cannot use value %d as %s", n, v.Type.NamedType)
}
rv = reflect.ValueOf(n)
} else if v.Type.NamedType == "Float" {
f, err := jsonNumber.Float64()
if err != nil {
return nil, gqlerror.ErrorPathf(validator.path, "cannot use value %f as %s", f, v.Type.NamedType)
}
rv = reflect.ValueOf(f)
}
}
if rv.Kind() == reflect.Ptr || rv.Kind() == reflect.Interface {
rv = rv.Elem()
}
rval, err := validator.validateVarType(v.Type, rv)
if err != nil {
return nil, err
}
coercedVars[v.Variable] = rval.Interface()
}
}
validator.path = validator.path[0 : len(validator.path)-1]
}
return coercedVars, nil
}
type varValidator struct {
path ast.Path
schema *ast.Schema
}
func (v *varValidator) validateVarType(typ *ast.Type, val reflect.Value) (reflect.Value, *gqlerror.Error) {
currentPath := v.path
resetPath := func() {
v.path = currentPath
}
defer resetPath()
if typ.Elem != nil {
if val.Kind() != reflect.Slice {
// GraphQL spec says that non-null values should be coerced to an array when possible.
// Hence if the value is not a slice, we create a slice and add val to it.
slc := reflect.MakeSlice(reflect.SliceOf(val.Type()), 0, 0)
slc = reflect.Append(slc, val)
val = slc
}
for i := range val.Len() {
resetPath()
v.path = append(v.path, ast.PathIndex(i))
field := val.Index(i)
if field.Kind() == reflect.Ptr || field.Kind() == reflect.Interface {
if typ.Elem.NonNull && field.IsNil() {
return val, gqlerror.ErrorPathf(v.path, "cannot be null")
}
field = field.Elem()
}
_, err := v.validateVarType(typ.Elem, field)
if err != nil {
return val, err
}
}
return val, nil
}
def := v.schema.Types[typ.NamedType]
if def == nil {
panic(fmt.Errorf("missing def for %s", typ.NamedType))
}
if !typ.NonNull && !val.IsValid() {
// If the type is not null and we got a invalid value namely null/nil, then it's valid
return val, nil
}
switch def.Kind {
case ast.Enum:
kind := val.Type().Kind()
if kind != reflect.Int && kind != reflect.Int32 && kind != reflect.Int64 && kind != reflect.String {
return val, gqlerror.ErrorPathf(v.path, "enums must be ints or strings")
}
isValidEnum := false
for _, enumVal := range def.EnumValues {
if strings.EqualFold(val.String(), enumVal.Name) {
isValidEnum = true
}
}
if !isValidEnum {
return val, gqlerror.ErrorPathf(v.path, "%s is not a valid %s", val.String(), def.Name)
}
return val, nil
case ast.Scalar:
kind := val.Type().Kind()
switch typ.NamedType {
case "Int":
if kind == reflect.Int || kind == reflect.Int32 || kind == reflect.Int64 || kind == reflect.Float32 || kind == reflect.Float64 || IsValidIntString(val, kind) {
return val, nil
}
case "Float":
if kind == reflect.Float32 || kind == reflect.Float64 || kind == reflect.Int || kind == reflect.Int32 || kind == reflect.Int64 || IsValidFloatString(val, kind) {
return val, nil
}
case "String":
if kind == reflect.String {
return val, nil
}
case "Boolean":
if kind == reflect.Bool {
return val, nil
}
case "ID":
if kind == reflect.Int || kind == reflect.Int32 || kind == reflect.Int64 || kind == reflect.String {
return val, nil
}
default:
// assume custom scalars are ok
return val, nil
}
return val, gqlerror.ErrorPathf(v.path, "cannot use %s as %s", kind.String(), typ.NamedType)
case ast.InputObject:
if val.Kind() != reflect.Map {
return val, gqlerror.ErrorPathf(v.path, "must be a %s", def.Name)
}
// check for unknown fields
for _, name := range val.MapKeys() {
val.MapIndex(name)
fieldDef := def.Fields.ForName(name.String())
resetPath()
v.path = append(v.path, ast.PathName(name.String()))
switch {
case name.String() == "__typename":
continue
case fieldDef == nil:
return val, gqlerror.ErrorPathf(v.path, "unknown field")
}
}
for _, fieldDef := range def.Fields {
resetPath()
v.path = append(v.path, ast.PathName(fieldDef.Name))
field := val.MapIndex(reflect.ValueOf(fieldDef.Name))
if !field.IsValid() {
if fieldDef.Type.NonNull {
if fieldDef.DefaultValue != nil {
var err error
_, err = fieldDef.DefaultValue.Value(nil)
if err == nil {
continue
}
}
return val, gqlerror.ErrorPathf(v.path, "must be defined")
}
continue
}
if field.Kind() == reflect.Ptr || field.Kind() == reflect.Interface {
if fieldDef.Type.NonNull && field.IsNil() {
return val, gqlerror.ErrorPathf(v.path, "cannot be null")
}
// allow null object field and skip it
if !fieldDef.Type.NonNull && field.IsNil() {
continue
}
field = field.Elem()
}
cval, err := v.validateVarType(fieldDef.Type, field)
if err != nil {
return val, err
}
val.SetMapIndex(reflect.ValueOf(fieldDef.Name), cval)
}
default:
panic(fmt.Errorf("unsupported type %s", def.Kind))
}
return val, nil
}
func IsValidIntString(val reflect.Value, kind reflect.Kind) bool {
if kind != reflect.String {
return false
}
_, e := strconv.ParseInt(fmt.Sprintf("%v", val.Interface()), 10, 64)
return e == nil
}
func IsValidFloatString(val reflect.Value, kind reflect.Kind) bool {
if kind != reflect.String {
return false
}
_, e := strconv.ParseFloat(fmt.Sprintf("%v", val.Interface()), 64)
return e == nil
}
@@ -1,292 +0,0 @@
package validator
import (
"context"
"fmt"
"github.com/open-policy-agent/opa/internal/gqlparser/ast"
)
type Events struct {
operationVisitor []func(walker *Walker, operation *ast.OperationDefinition)
field []func(walker *Walker, field *ast.Field)
fragment []func(walker *Walker, fragment *ast.FragmentDefinition)
inlineFragment []func(walker *Walker, inlineFragment *ast.InlineFragment)
fragmentSpread []func(walker *Walker, fragmentSpread *ast.FragmentSpread)
directive []func(walker *Walker, directive *ast.Directive)
directiveList []func(walker *Walker, directives []*ast.Directive)
value []func(walker *Walker, value *ast.Value)
variable []func(walker *Walker, variable *ast.VariableDefinition)
}
func (o *Events) OnOperation(f func(walker *Walker, operation *ast.OperationDefinition)) {
o.operationVisitor = append(o.operationVisitor, f)
}
func (o *Events) OnField(f func(walker *Walker, field *ast.Field)) {
o.field = append(o.field, f)
}
func (o *Events) OnFragment(f func(walker *Walker, fragment *ast.FragmentDefinition)) {
o.fragment = append(o.fragment, f)
}
func (o *Events) OnInlineFragment(f func(walker *Walker, inlineFragment *ast.InlineFragment)) {
o.inlineFragment = append(o.inlineFragment, f)
}
func (o *Events) OnFragmentSpread(f func(walker *Walker, fragmentSpread *ast.FragmentSpread)) {
o.fragmentSpread = append(o.fragmentSpread, f)
}
func (o *Events) OnDirective(f func(walker *Walker, directive *ast.Directive)) {
o.directive = append(o.directive, f)
}
func (o *Events) OnDirectiveList(f func(walker *Walker, directives []*ast.Directive)) {
o.directiveList = append(o.directiveList, f)
}
func (o *Events) OnValue(f func(walker *Walker, value *ast.Value)) {
o.value = append(o.value, f)
}
func (o *Events) OnVariable(f func(walker *Walker, variable *ast.VariableDefinition)) {
o.variable = append(o.variable, f)
}
func Walk(schema *ast.Schema, document *ast.QueryDocument, observers *Events) {
w := Walker{
Observers: observers,
Schema: schema,
Document: document,
}
w.walk()
}
type Walker struct {
Context context.Context
Observers *Events
Schema *ast.Schema
Document *ast.QueryDocument
validatedFragmentSpreads map[string]bool
CurrentOperation *ast.OperationDefinition
}
func (w *Walker) walk() {
for _, child := range w.Document.Operations {
w.validatedFragmentSpreads = make(map[string]bool)
w.walkOperation(child)
}
for _, child := range w.Document.Fragments {
w.validatedFragmentSpreads = make(map[string]bool)
w.walkFragment(child)
}
}
func (w *Walker) walkOperation(operation *ast.OperationDefinition) {
w.CurrentOperation = operation
for _, varDef := range operation.VariableDefinitions {
varDef.Definition = w.Schema.Types[varDef.Type.Name()]
for _, v := range w.Observers.variable {
v(w, varDef)
}
if varDef.DefaultValue != nil {
varDef.DefaultValue.ExpectedType = varDef.Type
varDef.DefaultValue.Definition = w.Schema.Types[varDef.Type.Name()]
}
}
var def *ast.Definition
var loc ast.DirectiveLocation
switch operation.Operation {
case ast.Query, "":
def = w.Schema.Query
loc = ast.LocationQuery
case ast.Mutation:
def = w.Schema.Mutation
loc = ast.LocationMutation
case ast.Subscription:
def = w.Schema.Subscription
loc = ast.LocationSubscription
}
for _, varDef := range operation.VariableDefinitions {
if varDef.DefaultValue != nil {
w.walkValue(varDef.DefaultValue)
}
w.walkDirectives(varDef.Definition, varDef.Directives, ast.LocationVariableDefinition)
}
w.walkDirectives(def, operation.Directives, loc)
w.walkSelectionSet(def, operation.SelectionSet)
for _, v := range w.Observers.operationVisitor {
v(w, operation)
}
w.CurrentOperation = nil
}
func (w *Walker) walkFragment(it *ast.FragmentDefinition) {
def := w.Schema.Types[it.TypeCondition]
it.Definition = def
w.walkDirectives(def, it.Directives, ast.LocationFragmentDefinition)
w.walkSelectionSet(def, it.SelectionSet)
for _, v := range w.Observers.fragment {
v(w, it)
}
}
func (w *Walker) walkDirectives(parentDef *ast.Definition, directives []*ast.Directive, location ast.DirectiveLocation) {
for _, dir := range directives {
def := w.Schema.Directives[dir.Name]
dir.Definition = def
dir.ParentDefinition = parentDef
dir.Location = location
for _, arg := range dir.Arguments {
var argDef *ast.ArgumentDefinition
if def != nil {
argDef = def.Arguments.ForName(arg.Name)
}
w.walkArgument(argDef, arg)
}
for _, v := range w.Observers.directive {
v(w, dir)
}
}
for _, v := range w.Observers.directiveList {
v(w, directives)
}
}
func (w *Walker) walkValue(value *ast.Value) {
if value.Kind == ast.Variable && w.CurrentOperation != nil {
value.VariableDefinition = w.CurrentOperation.VariableDefinitions.ForName(value.Raw)
if value.VariableDefinition != nil {
value.VariableDefinition.Used = true
}
}
if value.Kind == ast.ObjectValue {
for _, child := range value.Children {
if value.Definition != nil {
fieldDef := value.Definition.Fields.ForName(child.Name)
if fieldDef != nil {
child.Value.ExpectedType = fieldDef.Type
child.Value.Definition = w.Schema.Types[fieldDef.Type.Name()]
}
}
w.walkValue(child.Value)
}
}
if value.Kind == ast.ListValue {
for _, child := range value.Children {
if value.ExpectedType != nil && value.ExpectedType.Elem != nil {
child.Value.ExpectedType = value.ExpectedType.Elem
child.Value.Definition = value.Definition
}
w.walkValue(child.Value)
}
}
for _, v := range w.Observers.value {
v(w, value)
}
}
func (w *Walker) walkArgument(argDef *ast.ArgumentDefinition, arg *ast.Argument) {
if argDef != nil {
arg.Value.ExpectedType = argDef.Type
arg.Value.Definition = w.Schema.Types[argDef.Type.Name()]
}
w.walkValue(arg.Value)
}
func (w *Walker) walkSelectionSet(parentDef *ast.Definition, it ast.SelectionSet) {
for _, child := range it {
w.walkSelection(parentDef, child)
}
}
func (w *Walker) walkSelection(parentDef *ast.Definition, it ast.Selection) {
switch it := it.(type) {
case *ast.Field:
var def *ast.FieldDefinition
if it.Name == "__typename" {
def = &ast.FieldDefinition{
Name: "__typename",
Type: ast.NamedType("String", nil),
}
} else if parentDef != nil {
def = parentDef.Fields.ForName(it.Name)
}
it.Definition = def
it.ObjectDefinition = parentDef
var nextParentDef *ast.Definition
if def != nil {
nextParentDef = w.Schema.Types[def.Type.Name()]
}
for _, arg := range it.Arguments {
var argDef *ast.ArgumentDefinition
if def != nil {
argDef = def.Arguments.ForName(arg.Name)
}
w.walkArgument(argDef, arg)
}
w.walkDirectives(nextParentDef, it.Directives, ast.LocationField)
w.walkSelectionSet(nextParentDef, it.SelectionSet)
for _, v := range w.Observers.field {
v(w, it)
}
case *ast.InlineFragment:
it.ObjectDefinition = parentDef
nextParentDef := parentDef
if it.TypeCondition != "" {
nextParentDef = w.Schema.Types[it.TypeCondition]
}
w.walkDirectives(nextParentDef, it.Directives, ast.LocationInlineFragment)
w.walkSelectionSet(nextParentDef, it.SelectionSet)
for _, v := range w.Observers.inlineFragment {
v(w, it)
}
case *ast.FragmentSpread:
def := w.Document.Fragments.ForName(it.Name)
it.Definition = def
it.ObjectDefinition = parentDef
var nextParentDef *ast.Definition
if def != nil {
nextParentDef = w.Schema.Types[def.TypeCondition]
}
w.walkDirectives(nextParentDef, it.Directives, ast.LocationFragmentSpread)
if def != nil && !w.validatedFragmentSpreads[def.Name] {
// prevent inifinite recursion
w.validatedFragmentSpreads[def.Name] = true
w.walkSelectionSet(nextParentDef, def.SelectionSet)
}
for _, v := range w.Observers.fragmentSpread {
v(w, it)
}
default:
panic(fmt.Errorf("unsupported %T", it))
}
}
+1 -1
View File
@@ -21,7 +21,7 @@ const (
// Accept is used when conversion from values given by
// outside sources (such as JSON payloads) is required
func (keyType *KeyType) Accept(value interface{}) error {
func (keyType *KeyType) Accept(value any) error {
var tmp KeyType
switch x := value.(type) {
case string:
+1 -1
View File
@@ -32,7 +32,7 @@ const (
// Accept is used when conversion from values given by
// outside sources (such as JSON payloads) is required
func (signature *SignatureAlgorithm) Accept(value interface{}) error {
func (signature *SignatureAlgorithm) Accept(value any) error {
var tmp SignatureAlgorithm
switch x := value.(type) {
case string:
+2 -2
View File
@@ -39,12 +39,12 @@ func newECDSAPrivateKey(key *ecdsa.PrivateKey) (*ECDSAPrivateKey, error) {
}
// Materialize returns the EC-DSA public key represented by this JWK
func (k ECDSAPublicKey) Materialize() (interface{}, error) {
func (k ECDSAPublicKey) Materialize() (any, error) {
return k.key, nil
}
// Materialize returns the EC-DSA private key represented by this JWK
func (k ECDSAPrivateKey) Materialize() (interface{}, error) {
func (k ECDSAPrivateKey) Materialize() (any, error) {
return k.key, nil
}
+10 -10
View File
@@ -18,15 +18,15 @@ const (
// Headers provides a common interface to all future possible headers
type Headers interface {
Get(string) (interface{}, bool)
Set(string, interface{}) error
Walk(func(string, interface{}) error) error
Get(string) (any, bool)
Set(string, any) error
Walk(func(string, any) error) error
GetAlgorithm() jwa.SignatureAlgorithm
GetKeyID() string
GetKeyOps() KeyOperationList
GetKeyType() jwa.KeyType
GetKeyUsage() string
GetPrivateParams() map[string]interface{}
GetPrivateParams() map[string]any
}
// StandardHeaders stores the common JWK parameters
@@ -36,7 +36,7 @@ type StandardHeaders struct {
KeyOps KeyOperationList `json:"key_ops,omitempty"` // https://tools.ietf.org/html/rfc7517#section-4.3
KeyType jwa.KeyType `json:"kty,omitempty"` // https://tools.ietf.org/html/rfc7517#section-4.1
KeyUsage string `json:"use,omitempty"` // https://tools.ietf.org/html/rfc7517#section-4.2
PrivateParams map[string]interface{} `json:"privateParams,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.4
PrivateParams map[string]any `json:"privateParams,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.4
}
// GetAlgorithm is a convenience function to retrieve the corresponding value stored in the StandardHeaders
@@ -68,12 +68,12 @@ func (h *StandardHeaders) GetKeyUsage() string {
}
// GetPrivateParams is a convenience function to retrieve the corresponding value stored in the StandardHeaders
func (h *StandardHeaders) GetPrivateParams() map[string]interface{} {
func (h *StandardHeaders) GetPrivateParams() map[string]any {
return h.PrivateParams
}
// Get is a general getter function for JWK StandardHeaders structure
func (h *StandardHeaders) Get(name string) (interface{}, bool) {
func (h *StandardHeaders) Get(name string) (any, bool) {
switch name {
case AlgorithmKey:
alg := h.GetAlgorithm()
@@ -117,7 +117,7 @@ func (h *StandardHeaders) Get(name string) (interface{}, bool) {
}
// Set is a general getter function for JWK StandardHeaders structure
func (h *StandardHeaders) Set(name string, value interface{}) error {
func (h *StandardHeaders) Set(name string, value any) error {
switch name {
case AlgorithmKey:
var acceptor jwa.SignatureAlgorithm
@@ -149,7 +149,7 @@ func (h *StandardHeaders) Set(name string, value interface{}) error {
}
return fmt.Errorf("invalid value for %s key: %T", KeyUsageKey, value)
case PrivateParamsKey:
if v, ok := value.(map[string]interface{}); ok {
if v, ok := value.(map[string]any); ok {
h.PrivateParams = v
return nil
}
@@ -160,7 +160,7 @@ func (h *StandardHeaders) Set(name string, value interface{}) error {
}
// Walk iterates over all JWK standard headers fields while applying a function to its value.
func (h StandardHeaders) Walk(f func(string, interface{}) error) error {
func (h StandardHeaders) Walk(f func(string, any) error) error {
for _, key := range []string{AlgorithmKey, KeyIDKey, KeyOpsKey, KeyTypeKey, KeyUsageKey, PrivateParamsKey} {
if v, ok := h.Get(key); ok {
if err := f(key, v); err != nil {
+1 -1
View File
@@ -24,7 +24,7 @@ type Key interface {
// RSA types would create *rsa.PublicKey or *rsa.PrivateKey,
// EC types would create *ecdsa.PublicKey or *ecdsa.PrivateKey,
// and OctetSeq types create a []byte key.
Materialize() (interface{}, error)
Materialize() (any, error)
GenerateKey(*RawKeyJSON) error
}
+4 -4
View File
@@ -15,7 +15,7 @@ import (
// For rsa key types *rsa.PublicKey is returned; for ecdsa key types *ecdsa.PublicKey;
// for byte slice (raw) keys, the key itself is returned. If the corresponding
// public key cannot be deduced, an error is returned
func GetPublicKey(key interface{}) (interface{}, error) {
func GetPublicKey(key any) (any, error) {
if key == nil {
return nil, errors.New("jwk.New requires a non-nil key")
}
@@ -23,7 +23,7 @@ func GetPublicKey(key interface{}) (interface{}, error) {
switch v := key.(type) {
// Mental note: although Public() is defined in both types,
// you can not coalesce the clauses for rsa.PrivateKey and
// ecdsa.PrivateKey, as then `v` becomes interface{}
// ecdsa.PrivateKey, as then `v` becomes any
// b/c the compiler cannot deduce the exact type.
case *rsa.PrivateKey:
return v.Public(), nil
@@ -37,7 +37,7 @@ func GetPublicKey(key interface{}) (interface{}, error) {
}
// GetKeyTypeFromKey creates a jwk.Key from the given key.
func GetKeyTypeFromKey(key interface{}) jwa.KeyType {
func GetKeyTypeFromKey(key any) jwa.KeyType {
switch key.(type) {
case *rsa.PrivateKey, *rsa.PublicKey:
@@ -52,7 +52,7 @@ func GetKeyTypeFromKey(key interface{}) jwa.KeyType {
}
// New creates a jwk.Key from the given key.
func New(key interface{}) (Key, error) {
func New(key any) (Key, error) {
if key == nil {
return nil, errors.New("jwk.New requires a non-nil key")
}
+1 -1
View File
@@ -39,7 +39,7 @@ const (
)
// Accept determines if Key Operation is valid
func (keyOperationList *KeyOperationList) Accept(v interface{}) error {
func (keyOperationList *KeyOperationList) Accept(v any) error {
switch x := v.(type) {
case KeyOperationList:
*keyOperationList = x
+2 -2
View File
@@ -65,7 +65,7 @@ func newRSAPrivateKey(key *rsa.PrivateKey) (*RSAPrivateKey, error) {
}
// Materialize returns the standard RSA Public Key representation stored in the internal representation
func (k *RSAPublicKey) Materialize() (interface{}, error) {
func (k *RSAPublicKey) Materialize() (any, error) {
if k.key == nil {
return nil, errors.New("key has no rsa.PublicKey associated with it")
}
@@ -73,7 +73,7 @@ func (k *RSAPublicKey) Materialize() (interface{}, error) {
}
// Materialize returns the standard RSA Private Key representation stored in the internal representation
func (k *RSAPrivateKey) Materialize() (interface{}, error) {
func (k *RSAPrivateKey) Materialize() (any, error) {
if k.key == nil {
return nil, errors.New("key has no rsa.PrivateKey associated with it")
}
+1 -1
View File
@@ -21,7 +21,7 @@ func newSymmetricKey(key []byte) (*SymmetricKey, error) {
// Materialize returns the octets for this symmetric key.
// Since this is a symmetric key, this just calls Octets
func (s SymmetricKey) Materialize() (interface{}, error) {
func (s SymmetricKey) Materialize() (any, error) {
return s.Octets(), nil
}
+6 -6
View File
@@ -20,8 +20,8 @@ const (
// Headers provides a common interface for common header parameters
type Headers interface {
Get(string) (interface{}, bool)
Set(string, interface{}) error
Get(string) (any, bool)
Set(string, any) error
GetAlgorithm() jwa.SignatureAlgorithm
}
@@ -33,7 +33,7 @@ type StandardHeaders struct {
JWK string `json:"jwk,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.3
JWKSetURL string `json:"jku,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.2
KeyID string `json:"kid,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.4
PrivateParams map[string]interface{} `json:"privateParams,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.9
PrivateParams map[string]any `json:"privateParams,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.9
Type string `json:"typ,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.9
}
@@ -43,7 +43,7 @@ func (h *StandardHeaders) GetAlgorithm() jwa.SignatureAlgorithm {
}
// Get is a general getter function for StandardHeaders structure
func (h *StandardHeaders) Get(name string) (interface{}, bool) {
func (h *StandardHeaders) Get(name string) (any, bool) {
switch name {
case AlgorithmKey:
v := h.Algorithm
@@ -99,7 +99,7 @@ func (h *StandardHeaders) Get(name string) (interface{}, bool) {
}
// Set is a general setter function for StandardHeaders structure
func (h *StandardHeaders) Set(name string, value interface{}) error {
func (h *StandardHeaders) Set(name string, value any) error {
switch name {
case AlgorithmKey:
if err := h.Algorithm.Accept(value); err != nil {
@@ -137,7 +137,7 @@ func (h *StandardHeaders) Set(name string, value interface{}) error {
}
return fmt.Errorf("invalid value for %s key: %T", KeyIDKey, value)
case PrivateParamsKey:
if v, ok := value.(map[string]interface{}); ok {
if v, ok := value.(map[string]any); ok {
h.PrivateParams = v
return nil
}

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