Skip to content

Is input validation too loose for String and Boolean? #865

@Mark-K

Description

@Mark-K

The QA team here is asking me about the validation of Boolean input values.
I need to give them an answer.

The GraphQL specification for input Boolean and String values states:
Reference: http://facebook.github.io/graphql/October2016/#sec-Boolean-Value

2.9.3 Boolean Value
    BooleanValue: one of
       true    false
    The two keywords true and false represent the two boolean values.
2.9.4 String Value
    StringValue ::
        ""
        " StringCharacter list "
    [excerpted]
    Strings are sequences of characters wrapped in double‐quotes ("). (ex. "Hello World").
    White space and other otherwise‐ignored characters are significant within a string value.

Is this the reference specification used for the graphql-java implementation?

The same or a similar issue was recently addressed in graphql-js here: graphql/graphql-js#771

Example follows. The output is generated by the graphql java 6.0 release.
Schema Definition:

schema { query : Query }
type Query { ping ( input : Param ! ) : Ping }
input Param {
   s : String !
   b : Boolean !
}
type Ping {
   s : String
   b : Boolean
}

Sample requests and program output:

R-1:

{
    "query" : "query ( $input : Param ! ) { ping ( input : $input ) { s b }}"
    "variables" : {
        "input" : {
            "s" : 0
            "b" : 0
        }
    }
}

Output: {"data":{"ping":{"s":"0","b":false}}}

R-2:

{
    "query" : "query ( $input : Param ! ) { ping ( input : $input ) { s b }}"
    "variables" : {
        "input" : {
            "s" : 123345,
            "b" : 123345
        }
    }
}

Output: {"data":{"ping":{"s":"12345","b":true}}}

R-3:

{
    "query" : "query ( $input : Param ! ) { ping ( input : $input ) { s b } }"
    "variables" : {
        "input" : {
            "s" : "Atlanta",
            "b" : "Atlanta"
        }
    }
}

Output: {"data":{"ping":{"s":"Atlanta","b":false}}}

R-4:

{
    "query" : "query ( $input : Param ! ) { ping ( input : $input ) { s b } }"
    "variables" : {
        "input" : {
            "s" : [1,2,3],
            "b" : "Atlanta"
        }
    }
}

Output: {"data":{"ping":{"s":"[1, 2, 3]","b":false}}}

R-5:

{
    "query" : "query ( $input : Param ! ) { ping ( input : $input ) { s b } }"
    "variables" : {
        "input" : {
            "s" : [1,2,3],
            "b" : [1,2,3]
        }
    }
}

Output: {"errors":[{"message":"Invalid input '[1, 2, 3]' for Boolean"}]}

Output Expectations:
R-1, R-2 should return an error for either s or b because the value of s is not quoted and the value of b is neither true nor false.
R-3 should return return an error for b because the value of b is neither true nor false.
R-4 should return an error for s because the value of s is not quoted
R-5 is ok because an error is returned for b as expected; but depending on the order of validation an error for s might be returned instead.

The Java 8 Program used for testing:

package graphqltest;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
import graphql.schema.idl.TypeInfo;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class GraphQLTest {
    public static void main(String[] args) {
        SchemaParser parser = new SchemaParser();
        SchemaGenerator generator = new SchemaGenerator();
        TypeDefinitionRegistry registry = parser.parse("schema { query : Query } type Query { ping ( input : Param ! ) : Ping } input Param { s : String ! b : Boolean ! } type Ping{ s : String b : Boolean }");
        RuntimeWiring wiring = RuntimeWiring.newRuntimeWiring().type("Query", wires -> wires.dataFetcher("ping", env -> {
            if (env.containsArgument("input")) { return env.getArgument("input"); }
            return null;
        })).build();
        GraphQLSchema schema = generator.makeExecutableSchema(registry, wiring);
        GraphQL gql = GraphQL.newGraphQL(schema).build();
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule mod = new SimpleModule();
        mod.addSerializer(TypeInfo.class, new JsonSerializer<TypeInfo>() {
            @Override
            public void serialize(TypeInfo t, JsonGenerator jg, SerializerProvider sp) throws IOException, JsonProcessingException { jg.writeString(t.toString()); }
        });
        mapper.registerModule(mod);
        HashMapTypeReference typeRef = new HashMapTypeReference();
        String[] requests = new String[]{
            "{\"query\":\"query ( $input : Param ! ) { ping ( input : $input ) { s b }}\",\"variables\":{\"input\":{\"s\":0,\"b\":0}}}",                        // R-1
            "{\"query\":\"query ( $input : Param ! ) { ping ( input : $input ) { s b }}\",\"variables\":{\"input\":{\"s\":12345,\"b\":12345}}}",                // R-2
            "{\"query\":\"query ( $input : Param ! ) { ping ( input : $input ) { s b }}\",\"variables\":{\"input\":{\"s\":\"Atlanta\",\"b\":\"Atlanta\"}}}",    // R-3
            "{\"query\":\"query ( $input : Param ! ) { ping ( input : $input ) { s b }}\",\"variables\":{\"input\":{\"s\":[1,2,3],\"b\":\"Atlanta\"}}}",        // R-4
            "{\"query\":\"query ( $input : Param ! ) { ping ( input : $input ) { s b }}\",\"variables\":{\"input\":{\"s\":[1,2,3],\"b\":[1,2,3]}}}"             // R-5
        };
        for (String request : requests) {
            try {
                Map<String, Object> input = mapper.readValue(request, typeRef);
                String query = (String) input.get("query");
                Map<String, Object> variables = (Map<String, Object>) input.get("variables");
                ExecutionResult result = gql.execute(ExecutionInput.newExecutionInput().query(query).variables(variables).build());
                System.out.println("----------");
                System.out.println("Request: " + request);
                System.out.println(" Output: " + mapper.writeValueAsString(result.toSpecification()));
            } catch (Exception ex) {
                System.out.println(ex);
            }
        }
        System.out.append("----------");
    }
    @SuppressWarnings("PublicInnerClass")
    public static class HashMapTypeReference extends TypeReference<HashMap<String, Object>> { }
}

Libraries:

antlr-runtime-4.7.jar
graphql-java-6.0.jar
jackson-annotations-2.9.2.jar
jackson-core-2.9.2.jar
jackson-databind-2.9.2.jar
java-dataloader-2.0.1.jar
log4j-api-2.8.2.jar
log4j-core-2.8.2.jar
log4j-slf4j-impl-2.8.2.jar
slf4j-api-1.7.25.jar

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions