Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DSL does not provide expected argument validation #355

Open
brhubbar opened this issue Aug 23, 2022 · 1 comment
Open

DSL does not provide expected argument validation #355

brhubbar opened this issue Aug 23, 2022 · 1 comment
Labels
type: feature A new feature

Comments

@brhubbar
Copy link

brhubbar commented Aug 23, 2022

Describe the bug

  • When creating a query with gql.gql(), the query string is checked for invalid arguments, and raises an exception if any are found. (expected behavior)
  • When creating a query with gql.dsl.dsl_gql(), the arguments are not checked, causing unexpected return values. (unexpected behavior)

I've been able to recreate this using the countries api used in the docs.

To Reproduce

Jump to step 5 to see the actual improper behavior.

  1. Set up the transport/client.

    import json
    
    import gql
    from gql.transport.requests import RequestsHTTPTransport as Transport
    from gql import dsl
    
    url = "https://countries.trevorblades.com/"
    
    transport = Transport(url=url)
    client = gql.Client(transport=transport, fetch_schema_from_transport=True)
    
    # Fetch the schema (lemme know if there's a recommended approach for this).
    client.connect_sync()
    client.close_sync()
    ds = dsl.DSLSchema(client.schema)
  2. Run a good query using strings.

    good_query_str = gql.gql(
        """
        query {
            continents (filter:{code:{eq:"AN"}}) {
                code
                name
            }
        }
        """
    )
    result = client.execute(good_query_str)
    print(json.dumps(result, indent=2))

    Result:

     {
     "continents": [
         {
         "code": "AN",
         "name": "Antarctica"
         }
     ]
     }
  3. Run a bad query using strings. The only change here is using 'AN' directly as an argument to code, instead of providing the eq directive.

    bad_query_str = gql.gql(
        """
        query {
            continents (filter:{code:"AN"}) {
                code
                name
            }
        }
        """
    )
    result = client.execute(bad_query_str)
    print(json.dumps(result, indent=2))

    Result:

    GraphQLError: Expected value of type 'StringQueryOperatorInput', found "AN".
    
     GraphQL request:3:34
     2 |     query {
     3 |         continents (filter:{code:"AN"}) {
     |                                  ^
     4 |             code
  4. Run a good query using DSL.

    good_query_dsl = dsl.dsl_gql(
        dsl.DSLQuery(
            ds.Query.continents(
                filter={
                    'code': {'eq': 'AN'}
                }
            ).select(
                ds.Continent.code,
                ds.Continent.name,
            )
        )
    )
    result = client.execute(good_query_dsl)
    print(json.dumps(result, indent=2))

    Result:

     {
     "continents": [
         {
         "code": "AN",
         "name": "Antarctica"
         }
     ]
     }
  5. Run a bad query using DSL. Same deal, just remove the 'eq' level of filter specification. Note that the result is an unfiltered response.

    bad_query_dsl = dsl.dsl_gql(
        dsl.DSLQuery(
            ds.Query.continents(
                filter={
                    'code': 'AN'
                }
            ).select(
                ds.Continent.code,
                ds.Continent.name,
            )
        )
    )
    result = client.execute(bad_query_dsl)
    print(json.dumps(result, indent=2))

    Result:

     {
     "continents": [
         {
         "code": "AF",
         "name": "Africa"
         },
         {
         "code": "AN",
         "name": "Antarctica"
         },
         {
         "code": "AS",
         "name": "Asia"
         },
         {
         "code": "EU",
         "name": "Europe"
         },
         {
         "code": "NA",
         "name": "North America"
         },
         {
         "code": "OC",
         "name": "Oceania"
         },
         {
         "code": "SA",
         "name": "South America"
         }
     ]
     }

Expected behavior

Step 5 should raise an equivalent exception to step 3.

System info (please complete the following information):

  • OS: Wins 10
  • Python version: 3.9.12
  • gql version: 3.4.0
  • graphql-core version: 3.2.1
@leszekhanusz leszekhanusz added the type: feature A new feature label Sep 2, 2022
@brhubbar
Copy link
Author

Poking at this a bit more (after finding gql.dsl.print_ast), it looks like DSL silently cleanses your query of anything it doesn't recognize, so the example above:

from gql.dsl import print_ast

bad_query_dsl = dsl.dsl_gql(
    dsl.DSLQuery(
        ds.Query.continents(
            filter={
                'code': 'AN'
            }
        ).select(
            ds.Continent.code,
            ds.Continent.name,
        )
    )
)
print(print_ast(bad_query_dsl))

Results in this query:

{
  continents(filter: {}) {
    code
    name
  }
}

This also happens in some (but not all) cases where an incorrect type is provided as a value. I've found that passing a list where a string is expected returns a very long traceback, ending with this:

in serialize_string(output_value)
    174 # do not serialize builtin types as strings, but allow serialization of custom    175 # types via their `__str__` method
    176 if type(output_value).__module__ == "builtins":
--> 177     raise GraphQLError("String cannot represent value: " + inspect(output_value))
    178 return str(output_value)

GraphQLError: String cannot represent value: ['AN']

However, passing a string or a dict where a list of dict is expected results in an empty list in the query. Differently again, passing a string where a list of string is expected is accepted. I don't know if this is a gql quirk or a GraphQL quirk.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: feature A new feature
Projects
None yet
Development

No branches or pull requests

2 participants