mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-12 19:32:48 -06:00
topdown/builtins: Add object support for GraphQL builtins. (#4752)
This commit adds support for AST objects to be usable in place of strings for several of the GraphQL built-in functions, to improve the composability of the GraphQL set of built-ins, and to dramatically reduce the amount of redundant parsing when writing GraphQL policies. Fixes: #4742 Signed-off-by: Philip Conrad <philipaconrad@gmail.com>
This commit is contained in:
+9
-9
@@ -2516,11 +2516,11 @@ var HTTPSend = &Builtin{
|
||||
// GraphQLParse returns a pair of AST objects from parsing/validation.
|
||||
var GraphQLParse = &Builtin{
|
||||
Name: "graphql.parse",
|
||||
Description: "Returns AST objects for a given GraphQL query and schema after validating the query against the schema. Returns undefined if errors were encountered during parsing or validation.",
|
||||
Description: "Returns AST objects for a given GraphQL query and schema after validating the query against the schema. Returns undefined if errors were encountered during parsing or validation. The query and/or schema can be either GraphQL strings or AST objects from the other GraphQL builtin functions.",
|
||||
Decl: types.NewFunction(
|
||||
types.Args(
|
||||
types.Named("query", types.S),
|
||||
types.Named("schema", types.S),
|
||||
types.Named("query", types.NewAny(types.S, types.NewObject(nil, types.NewDynamicProperty(types.A, types.A)))),
|
||||
types.Named("schema", types.NewAny(types.S, types.NewObject(nil, types.NewDynamicProperty(types.A, types.A)))),
|
||||
),
|
||||
types.Named("output", types.NewArray([]types.Type{
|
||||
types.NewObject(nil, types.NewDynamicProperty(types.A, types.A)),
|
||||
@@ -2532,11 +2532,11 @@ var GraphQLParse = &Builtin{
|
||||
// GraphQLParseAndVerify returns a boolean and a pair of AST object from parsing/validation.
|
||||
var GraphQLParseAndVerify = &Builtin{
|
||||
Name: "graphql.parse_and_verify",
|
||||
Description: "Returns a boolean indicating success or failure alongside the parsed ASTs for a given GraphQL query and schema after validating the query against the schema.",
|
||||
Description: "Returns a boolean indicating success or failure alongside the parsed ASTs for a given GraphQL query and schema after validating the query against the schema. The query and/or schema can be either GraphQL strings or AST objects from the other GraphQL builtin functions.",
|
||||
Decl: types.NewFunction(
|
||||
types.Args(
|
||||
types.Named("query", types.S),
|
||||
types.Named("schema", types.S),
|
||||
types.Named("query", types.NewAny(types.S, types.NewObject(nil, types.NewDynamicProperty(types.A, types.A)))),
|
||||
types.Named("schema", types.NewAny(types.S, types.NewObject(nil, types.NewDynamicProperty(types.A, types.A)))),
|
||||
),
|
||||
types.Named("output", types.NewArray([]types.Type{
|
||||
types.B,
|
||||
@@ -2576,11 +2576,11 @@ var GraphQLParseSchema = &Builtin{
|
||||
// schema, and returns false for all other inputs.
|
||||
var GraphQLIsValid = &Builtin{
|
||||
Name: "graphql.is_valid",
|
||||
Description: "Checks that a GraphQL query is valid against a given schema.",
|
||||
Description: "Checks that a GraphQL query is valid against a given schema. The query and/or schema can be either GraphQL strings or AST objects from the other GraphQL builtin functions.",
|
||||
Decl: types.NewFunction(
|
||||
types.Args(
|
||||
types.Named("query", types.S),
|
||||
types.Named("schema", types.S),
|
||||
types.Named("query", types.NewAny(types.S, types.NewObject(nil, types.NewDynamicProperty(types.A, types.A)))),
|
||||
types.Named("schema", types.NewAny(types.S, types.NewObject(nil, types.NewDynamicProperty(types.A, types.A)))),
|
||||
),
|
||||
types.Named("output", types.B).Description("`true` if the query is valid under the given schema. `false` otherwise."),
|
||||
),
|
||||
|
||||
@@ -4125,11 +4125,11 @@
|
||||
"args": [
|
||||
{
|
||||
"name": "query",
|
||||
"type": "string"
|
||||
"type": "any\u003cstring, object[any: any]\u003e"
|
||||
},
|
||||
{
|
||||
"name": "schema",
|
||||
"type": "string"
|
||||
"type": "any\u003cstring, object[any: any]\u003e"
|
||||
}
|
||||
],
|
||||
"available": [
|
||||
@@ -4142,7 +4142,7 @@
|
||||
"v0.44.0",
|
||||
"edge"
|
||||
],
|
||||
"description": "Checks that a GraphQL query is valid against a given schema.",
|
||||
"description": "Checks that a GraphQL query is valid against a given schema. The query and/or schema can be either GraphQL strings or AST objects from the other GraphQL builtin functions.",
|
||||
"introduced": "v0.41.0",
|
||||
"result": {
|
||||
"description": "`true` if the query is valid under the given schema. `false` otherwise.",
|
||||
@@ -4155,11 +4155,11 @@
|
||||
"args": [
|
||||
{
|
||||
"name": "query",
|
||||
"type": "string"
|
||||
"type": "any\u003cstring, object[any: any]\u003e"
|
||||
},
|
||||
{
|
||||
"name": "schema",
|
||||
"type": "string"
|
||||
"type": "any\u003cstring, object[any: any]\u003e"
|
||||
}
|
||||
],
|
||||
"available": [
|
||||
@@ -4172,7 +4172,7 @@
|
||||
"v0.44.0",
|
||||
"edge"
|
||||
],
|
||||
"description": "Returns AST objects for a given GraphQL query and schema after validating the query against the schema. Returns undefined if errors were encountered during parsing or validation.",
|
||||
"description": "Returns AST objects for a given GraphQL query and schema after validating the query against the schema. Returns undefined if errors were encountered during parsing or validation. The query and/or schema can be either GraphQL strings or AST objects from the other GraphQL builtin functions.",
|
||||
"introduced": "v0.41.0",
|
||||
"result": {
|
||||
"description": "`output` is of the form `[query_ast, schema_ast]`. If the GraphQL query is valid given the provided schema, then `query_ast` and `schema_ast` are objects describing the ASTs for the query and schema.",
|
||||
@@ -4185,11 +4185,11 @@
|
||||
"args": [
|
||||
{
|
||||
"name": "query",
|
||||
"type": "string"
|
||||
"type": "any\u003cstring, object[any: any]\u003e"
|
||||
},
|
||||
{
|
||||
"name": "schema",
|
||||
"type": "string"
|
||||
"type": "any\u003cstring, object[any: any]\u003e"
|
||||
}
|
||||
],
|
||||
"available": [
|
||||
@@ -4202,7 +4202,7 @@
|
||||
"v0.44.0",
|
||||
"edge"
|
||||
],
|
||||
"description": "Returns a boolean indicating success or failure alongside the parsed ASTs for a given GraphQL query and schema after validating the query against the schema.",
|
||||
"description": "Returns a boolean indicating success or failure alongside the parsed ASTs for a given GraphQL query and schema after validating the query against the schema. The query and/or schema can be either GraphQL strings or AST objects from the other GraphQL builtin functions.",
|
||||
"introduced": "v0.41.0",
|
||||
"result": {
|
||||
"description": " `output` is of the form `[valid, query_ast, schema_ast]`. If the query is valid given the provided schema, then `valid` is `true`, and `query_ast` and `schema_ast` are objects describing the ASTs for the GraphQL query and schema. Otherwise, `valid` is `false` and `query_ast` and `schema_ast` are `{}`.",
|
||||
|
||||
+102
-6
@@ -1051,10 +1051,42 @@
|
||||
"decl": {
|
||||
"args": [
|
||||
{
|
||||
"type": "string"
|
||||
"of": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"dynamic": {
|
||||
"key": {
|
||||
"type": "any"
|
||||
},
|
||||
"value": {
|
||||
"type": "any"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
],
|
||||
"type": "any"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
"of": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"dynamic": {
|
||||
"key": {
|
||||
"type": "any"
|
||||
},
|
||||
"value": {
|
||||
"type": "any"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
],
|
||||
"type": "any"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
@@ -1068,10 +1100,42 @@
|
||||
"decl": {
|
||||
"args": [
|
||||
{
|
||||
"type": "string"
|
||||
"of": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"dynamic": {
|
||||
"key": {
|
||||
"type": "any"
|
||||
},
|
||||
"value": {
|
||||
"type": "any"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
],
|
||||
"type": "any"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
"of": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"dynamic": {
|
||||
"key": {
|
||||
"type": "any"
|
||||
},
|
||||
"value": {
|
||||
"type": "any"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
],
|
||||
"type": "any"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
@@ -1109,10 +1173,42 @@
|
||||
"decl": {
|
||||
"args": [
|
||||
{
|
||||
"type": "string"
|
||||
"of": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"dynamic": {
|
||||
"key": {
|
||||
"type": "any"
|
||||
},
|
||||
"value": {
|
||||
"type": "any"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
],
|
||||
"type": "any"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
"of": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"dynamic": {
|
||||
"key": {
|
||||
"type": "any"
|
||||
},
|
||||
"value": {
|
||||
"type": "any"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
],
|
||||
"type": "any"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
|
||||
@@ -41,7 +41,7 @@ schema {
|
||||
}
|
||||
|
||||
type Query {
|
||||
userByID(id: String): Employee
|
||||
employeeByID(id: String): Employee
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -145,3 +145,31 @@ cases:
|
||||
query: data.test.p = x
|
||||
want_result:
|
||||
- x: true
|
||||
- data:
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
schema_ast := graphql.parse_schema(`
|
||||
type Employee {
|
||||
id: String!
|
||||
salary: Int!
|
||||
}
|
||||
|
||||
schema {
|
||||
query: Query
|
||||
}
|
||||
|
||||
type Query {
|
||||
employeeByID(id: String): Employee
|
||||
}
|
||||
`)
|
||||
query_ast := graphql.parse_query(`
|
||||
query { employeeByID(id: "alice") { salary }}
|
||||
`)
|
||||
p {
|
||||
graphql.is_valid(query_ast, schema_ast)
|
||||
}
|
||||
note: graphql_is_valid/success - AST objects - Employee example
|
||||
query: data.test.p = x
|
||||
want_result:
|
||||
- x: true
|
||||
|
||||
File diff suppressed because one or more lines are too long
+31
-3
File diff suppressed because one or more lines are too long
+160
-85
@@ -5,40 +5,22 @@
|
||||
package topdown
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
gqltop "github.com/open-policy-agent/opa/internal/gqlparser"
|
||||
|
||||
gqlast "github.com/open-policy-agent/opa/internal/gqlparser/ast"
|
||||
gqlparser "github.com/open-policy-agent/opa/internal/gqlparser/parser"
|
||||
gqlvalidator "github.com/open-policy-agent/opa/internal/gqlparser/validator"
|
||||
|
||||
// Side-effecting import. Triggers GraphQL library's validation rule init() functions.
|
||||
_ "github.com/open-policy-agent/opa/internal/gqlparser/validator/rules"
|
||||
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
"github.com/open-policy-agent/opa/topdown/builtins"
|
||||
)
|
||||
|
||||
// Parses a GraphQL schema, and returns only the Schema object.
|
||||
// Used in validation of queries.
|
||||
// NOTE(philipc): The error type here is a gqlerror.Error struct,
|
||||
// which requires us to treat it specially, since the returned value
|
||||
// will *always* be non-null.
|
||||
// See: https://staticcheck.io/docs/checks#SA4023 for details.
|
||||
func loadSchema(schema string) (*gqlast.Schema, error) {
|
||||
loadedSchema, err := gqltop.LoadSchema(&gqlast.Source{Input: schema})
|
||||
if err != nil {
|
||||
errorParts := strings.SplitN(err.Error(), ":", 4)
|
||||
msg := strings.TrimLeft(errorParts[3], " ")
|
||||
return nil, fmt.Errorf("%s in GraphQL schema string at location %s:%s", msg, errorParts[1], errorParts[2])
|
||||
}
|
||||
return loadedSchema, nil
|
||||
}
|
||||
|
||||
// Parses a GraphQL schema, and returns the GraphQL AST for the schema.
|
||||
// NOTE(philipc): The error type here is a gqlerror.Error struct,
|
||||
// which requires us to treat it specially, since the returned value
|
||||
// will *always* be non-null.
|
||||
// See: https://staticcheck.io/docs/checks#SA4023 for details.
|
||||
func parseSchema(schema string) (*gqlast.SchemaDocument, error) {
|
||||
// NOTE(philipc): We don't include the "built-in schema defs" from the
|
||||
// underlying graphql parsing library here, because those definitions
|
||||
@@ -56,10 +38,6 @@ func parseSchema(schema string) (*gqlast.SchemaDocument, error) {
|
||||
}
|
||||
|
||||
// Parses a GraphQL query, and returns the GraphQL AST for the query.
|
||||
// NOTE(philipc): The error type here is a gqlerror.Error struct,
|
||||
// which requires us to treat it specially, since the returned value
|
||||
// will *always* be non-null.
|
||||
// See: https://staticcheck.io/docs/checks#SA4023 for details.
|
||||
func parseQuery(query string) (*gqlast.QueryDocument, error) {
|
||||
queryAST, err := gqlparser.ParseQuery(&gqlast.Source{Input: query})
|
||||
if err != nil {
|
||||
@@ -73,10 +51,6 @@ func parseQuery(query string) (*gqlast.QueryDocument, error) {
|
||||
// Validates a GraphQL query against a schema, and returns an error.
|
||||
// In this case, we get a wrappered error list type, and pluck out
|
||||
// just the first error message in the list.
|
||||
// NOTE(philipc): The error type from Validate() is a gqlerror.List
|
||||
// struct, which requires us to treat it specially, since the
|
||||
// returned value will *always* be non-null.
|
||||
// See: https://staticcheck.io/docs/checks#SA4023 for details.
|
||||
func validateQuery(schema *gqlast.Schema, query *gqlast.QueryDocument) error {
|
||||
// Validate the query against the schema, erroring if there's an issue.
|
||||
err := gqlvalidator.Validate(schema, query)
|
||||
@@ -94,6 +68,78 @@ func validateQuery(schema *gqlast.Schema, query *gqlast.QueryDocument) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getBuiltinSchema() *gqlast.SchemaDocument {
|
||||
schema, err := gqlparser.ParseSchema(gqlvalidator.Prelude)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error in gqlparser Prelude (should be impossible): %w", err))
|
||||
}
|
||||
return schema
|
||||
}
|
||||
|
||||
// NOTE(philipc): This function expects *validated* schema documents, and will break
|
||||
// if it is fed arbitrary structures.
|
||||
func mergeSchemaDocuments(docA *gqlast.SchemaDocument, docB *gqlast.SchemaDocument) *gqlast.SchemaDocument {
|
||||
ast := &gqlast.SchemaDocument{}
|
||||
ast.Merge(docA)
|
||||
ast.Merge(docB)
|
||||
return ast
|
||||
}
|
||||
|
||||
// Converts a SchemaDocument into a gqlast.Schema object that can be used for validation.
|
||||
// It merges in the builtin schema typedefs exactly as gqltop.LoadSchema did internally.
|
||||
func convertSchema(schemaDoc *gqlast.SchemaDocument) (*gqlast.Schema, error) {
|
||||
// Merge builtin schema + schema we were provided.
|
||||
builtinsSchemaDoc := getBuiltinSchema()
|
||||
mergedSchemaDoc := mergeSchemaDocuments(builtinsSchemaDoc, schemaDoc)
|
||||
schema, err := gqlvalidator.ValidateSchemaDocument(mergedSchemaDoc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error in gqlparser SchemaDocument to Schema conversion: %w", err)
|
||||
}
|
||||
return schema, nil
|
||||
}
|
||||
|
||||
// Converts an ast.Object into a gqlast.QueryDocument object.
|
||||
func objectToQueryDocument(value ast.Object) (*gqlast.QueryDocument, error) {
|
||||
// Convert ast.Term to interface{} for JSON encoding below.
|
||||
asJSON, err := ast.JSON(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Marshal to JSON.
|
||||
bs, err := json.Marshal(asJSON)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Unmarshal from JSON -> gqlast.QueryDocument.
|
||||
var result gqlast.QueryDocument
|
||||
err = json.Unmarshal(bs, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// Converts an ast.Object into a gqlast.SchemaDocument object.
|
||||
func objectToSchemaDocument(value ast.Object) (*gqlast.SchemaDocument, error) {
|
||||
// Convert ast.Term to interface{} for JSON encoding below.
|
||||
asJSON, err := ast.JSON(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Marshal to JSON.
|
||||
bs, err := json.Marshal(asJSON)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Unmarshal from JSON -> gqlast.SchemaDocument.
|
||||
var result gqlast.SchemaDocument
|
||||
err = json.Unmarshal(bs, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// Recursively traverses an AST that has been run through InterfaceToValue,
|
||||
// and prunes away the fields with null or empty values, and all `Position`
|
||||
// structs.
|
||||
@@ -178,35 +224,34 @@ func pruneIrrelevantGraphQLASTNodes(value ast.Value) ast.Value {
|
||||
|
||||
// Reports errors from parsing/validation.
|
||||
func builtinGraphQLParse(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
// Get the raw strings from each operand so that we can
|
||||
// feed them to the GraphQL parser functions.
|
||||
rawQuery, err := builtins.StringOperand(operands[0].Value, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
var queryDoc *gqlast.QueryDocument
|
||||
var schemaDoc *gqlast.SchemaDocument
|
||||
var err error
|
||||
|
||||
// Parse/translate query if it's a string/object.
|
||||
switch x := operands[0].Value.(type) {
|
||||
case ast.String:
|
||||
queryDoc, err = parseQuery(string(x))
|
||||
case ast.Object:
|
||||
queryDoc, err = objectToQueryDocument(x)
|
||||
default:
|
||||
// Error if wrong type.
|
||||
return builtins.NewOperandTypeErr(0, x, "string", "object")
|
||||
}
|
||||
rawSchema, err := builtins.StringOperand(operands[1].Value, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate ASTs/errors for the GraphQL query.
|
||||
queryDoc, err := parseQuery(string(rawQuery))
|
||||
if err != nil {
|
||||
return err
|
||||
// Parse/translate schema if it's a string/object.
|
||||
switch x := operands[1].Value.(type) {
|
||||
case ast.String:
|
||||
schemaDoc, err = parseSchema(string(x))
|
||||
case ast.Object:
|
||||
schemaDoc, err = objectToSchemaDocument(x)
|
||||
default:
|
||||
// Error if wrong type.
|
||||
return builtins.NewOperandTypeErr(1, x, "string", "object")
|
||||
}
|
||||
|
||||
// Validate the query against the schema, erroring if there's an issue.
|
||||
schema, err := loadSchema(string(rawSchema))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateQuery(schema, queryDoc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate AST/errors for the GraphQL schema, since the query
|
||||
// passed validation.
|
||||
schemaDoc, err := parseSchema(string(rawSchema))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -221,6 +266,15 @@ func builtinGraphQLParse(_ BuiltinContext, operands []*ast.Term, iter func(*ast.
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate the query against the schema, erroring if there's an issue.
|
||||
schema, err := convertSchema(schemaDoc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateQuery(schema, queryDoc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Recursively remove irrelevant AST structures.
|
||||
queryResult := pruneIrrelevantGraphQLASTNodes(queryASTValue.(ast.Object))
|
||||
querySchema := pruneIrrelevantGraphQLASTNodes(schemaASTValue.(ast.Object))
|
||||
@@ -236,16 +290,9 @@ func builtinGraphQLParse(_ BuiltinContext, operands []*ast.Term, iter func(*ast.
|
||||
|
||||
// Returns default value when errors occur.
|
||||
func builtinGraphQLParseAndVerify(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
// Get the raw strings from each operand so that we can
|
||||
// feed them to the GraphQL parser functions.
|
||||
rawQuery, err := builtins.StringOperand(operands[0].Value, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rawSchema, err := builtins.StringOperand(operands[1].Value, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var queryDoc *gqlast.QueryDocument
|
||||
var schemaDoc *gqlast.SchemaDocument
|
||||
var err error
|
||||
|
||||
unverified := ast.ArrayTerm(
|
||||
ast.BooleanTerm(false),
|
||||
@@ -253,24 +300,30 @@ func builtinGraphQLParseAndVerify(_ BuiltinContext, operands []*ast.Term, iter f
|
||||
ast.NewTerm(ast.NewObject()),
|
||||
)
|
||||
|
||||
// Generate ASTs/errors for the GraphQL query.
|
||||
queryDoc, err := parseQuery(string(rawQuery))
|
||||
// Parse/translate query if it's a string/object.
|
||||
switch x := operands[0].Value.(type) {
|
||||
case ast.String:
|
||||
queryDoc, err = parseQuery(string(x))
|
||||
case ast.Object:
|
||||
queryDoc, err = objectToQueryDocument(x)
|
||||
default:
|
||||
// Error if wrong type.
|
||||
return iter(unverified)
|
||||
}
|
||||
if err != nil {
|
||||
return iter(unverified)
|
||||
}
|
||||
|
||||
// Validate the query against the schema, erroring if there's an issue.
|
||||
schema, err := loadSchema(string(rawSchema))
|
||||
if err != nil {
|
||||
// Parse/translate schema if it's a string/object.
|
||||
switch x := operands[1].Value.(type) {
|
||||
case ast.String:
|
||||
schemaDoc, err = parseSchema(string(x))
|
||||
case ast.Object:
|
||||
schemaDoc, err = objectToSchemaDocument(x)
|
||||
default:
|
||||
// Error if wrong type.
|
||||
return iter(unverified)
|
||||
}
|
||||
if err := validateQuery(schema, queryDoc); err != nil {
|
||||
return iter(unverified)
|
||||
}
|
||||
|
||||
// Generate AST/errors for the GraphQL schema, since the query
|
||||
// passed validation.
|
||||
schemaDoc, err := parseSchema(string(rawSchema))
|
||||
if err != nil {
|
||||
return iter(unverified)
|
||||
}
|
||||
@@ -285,6 +338,15 @@ func builtinGraphQLParseAndVerify(_ BuiltinContext, operands []*ast.Term, iter f
|
||||
return iter(unverified)
|
||||
}
|
||||
|
||||
// Validate the query against the schema, erroring if there's an issue.
|
||||
schema, err := convertSchema(schemaDoc)
|
||||
if err != nil {
|
||||
return iter(unverified)
|
||||
}
|
||||
if err := validateQuery(schema, queryDoc); err != nil {
|
||||
return iter(unverified)
|
||||
}
|
||||
|
||||
// Recursively remove irrelevant AST structures.
|
||||
queryResult := pruneIrrelevantGraphQLASTNodes(queryASTValue.(ast.Object))
|
||||
querySchema := pruneIrrelevantGraphQLASTNodes(schemaASTValue.(ast.Object))
|
||||
@@ -348,29 +410,42 @@ func builtinGraphQLParseSchema(_ BuiltinContext, operands []*ast.Term, iter func
|
||||
}
|
||||
|
||||
func builtinGraphQLIsValid(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
// Get the raw strings from each operand so that we can
|
||||
// feed them to the GraphQL parser functions.
|
||||
rawQuery, err := builtins.StringOperand(operands[0].Value, 1)
|
||||
if err != nil {
|
||||
var queryDoc *gqlast.QueryDocument
|
||||
var schemaDoc *gqlast.SchemaDocument
|
||||
var err error
|
||||
|
||||
switch x := operands[0].Value.(type) {
|
||||
case ast.String:
|
||||
queryDoc, err = parseQuery(string(x))
|
||||
case ast.Object:
|
||||
queryDoc, err = objectToQueryDocument(x)
|
||||
default:
|
||||
// Error if wrong type.
|
||||
return iter(ast.BooleanTerm(false))
|
||||
}
|
||||
rawSchema, err := builtins.StringOperand(operands[1].Value, 1)
|
||||
if err != nil {
|
||||
return iter(ast.BooleanTerm(false))
|
||||
}
|
||||
|
||||
// Generate ASTs/errors for the GraphQL schema and query.
|
||||
schema, err := loadSchema(string(rawSchema))
|
||||
if err != nil {
|
||||
switch x := operands[1].Value.(type) {
|
||||
case ast.String:
|
||||
schemaDoc, err = parseSchema(string(x))
|
||||
case ast.Object:
|
||||
schemaDoc, err = objectToSchemaDocument(x)
|
||||
default:
|
||||
// Error if wrong type.
|
||||
return iter(ast.BooleanTerm(false))
|
||||
}
|
||||
query, err := parseQuery(string(rawQuery))
|
||||
if err != nil {
|
||||
return iter(ast.BooleanTerm(false))
|
||||
}
|
||||
|
||||
// Validate the query against the schema, erroring if there's an issue.
|
||||
if err := validateQuery(schema, query); err != nil {
|
||||
schema, err := convertSchema(schemaDoc)
|
||||
if err != nil {
|
||||
return iter(ast.BooleanTerm(false))
|
||||
}
|
||||
if err := validateQuery(schema, queryDoc); err != nil {
|
||||
return iter(ast.BooleanTerm(false))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user