question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Is input validation too loose for String and Boolean?

See original GitHub issue

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: https://github.com/graphql/graphql-js/issues/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

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
Mark-Kcommented, Dec 18, 2017

Thank you for looking into this very difficult and potentially contentious issue.

I will look into how to do the pull request and making groovy spock tests for reporting future issues. Thank you for the suggestion.

My thinking is that when the input data is JSON, the Map<String, Object> received as Input should have already been parsed by the JSON library’s (such as Jackson) string to Map<String, Object> parser and converted into the Java type as specified in the input data and JSON value conversion rules. So in the case of correct value syntax, there should be no need for value coercion.

For example: JSON: { "s": "s", "b": true, "n": 123 } should be parsed into Java Map<String, Object> { "s": String("s"), "b": Boolean.TRUE, "n": Integer(123) }

As a result, the graphql-java library could require that the values of built-in scalars in the Input Map be the type specified by the schema, since the built-in scalars are basically the same as those defined by the JSON format. Though there may need to be some leniency with numeric type conversions such as up casting (Int -> Long) when necessary.

This would mean that the code that prepares the variable map must do “the right thing”.
But that might not be current practice or not always reasonable, so making “strict” a requirement could break existing code and/or make parsing other data formats much more difficult. (i.e. web form data, Properties…)

My suggestion, would be (if the code will be modified) if it is feasible to make strict and lenient input validation selectable (with the default being lenient perhaps so as not to break anyone’s hard work). When explicitly set to “strict”, values in the input map must be the type specified by the schema without coercion. The “lenient” selection would provide the current (best effort to make sense out of the input) behavior.

This could be implemented in any of several different ways, for example:

  • an added boolean flag that changes the behavior of the existing validator
  • making input validation modular in the sense that a developer could select between a “StrictInputValidator” module, the “DefaultInputValidator” module or even implement a “UserDefinedInputValidator”
  • define pluggable “pre-validation” that executes just before field validation occurs
0reactions
andimarekcommented, Jul 19, 2019

closing this fairly old issue now . Please open a new issue against the current graphql java version if there is still a problem with input coercion. I am sorry that we didn’t address this topic in time.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Developers - Is input validation too loose for String and Boolean? -
The QA team here is asking me about the validation of Boolean input values. I need to give them an answer. The GraphQL...
Read more >
Propositions vs. boolean values for input validation
After checking the input is not empty I would like to pass it to a function which accepts only validated input (not empty...
Read more >
International nightmare with boolean inputs
I have a static Excel table with records that contain boolean flags. ... Via Data Validation, I can only constrain pure text strings....
Read more >
lots of useful validators — FormEncode 0.0.0 documentation
Invalid if the value is shorter than minlength . Uses len(), so it can work for strings, lists, or anything with length. Note...
Read more >
Validations Module - MuleSoft Documentation
Jump to Validate Size Validate Size ... Validates that the input's size is between given min and max boundaries. It's valid for inputs...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found