Skip to content

Support GraphQL variables in serialized requests instead of inlining input literals (upstream #559) #125

Description

@Besi97

Context

Upstream issue kobylynskyi#559
asked for request parameters to be sent in the standard "variables" JSON section
({"query": "query($name:String){...}", "variables": {"name":"x"}}) instead of being
inlined into the query string. It was closed as a question with the reasoning that
servers accept inlined literals fine.

That reasoning misses real cases:

  • Query complexity / depth limits: many servers (e.g. graphql-java's
    MaxQueryComplexityInstrumentation, Apollo Gateway, Hasura) compute complexity over
    the parsed document; inlined literals inflate the query text and defeat
    normalized-query caching / persisted queries, since every distinct value
    produces a different query document.
  • Strict endpoints (the original reporter's ArangoDB Foxx case) that require proper
    variables separation.

Verification (this fork, current code)

Behavior is exactly as described in kobylynskyi#559 — variables are not supported:

  • GraphQLRequestSerializer.buildQuery() inlines every input map entry via getEntry()
    (src/main/java/io/github/besi97/graphql/codegen/model/graphql/GraphQLRequestSerializer.java:100).
  • jsonQuery() emits only {"query":"..."}; no variables key is ever produced (:139).
  • "variables" appears nowhere in src/main or src/test.
  • Generated request classes only carry Map<String, Object> input — no GraphQL type info
    is available at runtime, so variable definitions ($name: String!) cannot even be built.

Proposed design (full scope)

Variables become the default for freshly generated code; a codegen option
(serializeRequestInputAsVariables, default true) reverts to inlined literals.

Top-level operation arguments

  • GraphQLOperationRequest: add default Map<String, String> getParamToGraphQLTypeMap()
    (arg name → GraphQL type, e.g. "id" -> "ID!"). Type map present ⇒ variables mode;
    absent ⇒ inline (backward compatible for hand-written requests).
  • ParameterDefinition: add graphQLType; populate in InputValueDefinitionToParameterMapper
    via a new recursive AST→string helper in GraphQLTypeMapper ([ID!]!, etc.).
  • Emit the map in java-lang/request.ftl + kotlin-lang/request.ftl.
  • GraphQLParametrizedInput + both parametrized_input.ftls: add getInput() returning the
    raw field values (fields are private, no getters today) so nested input objects can be
    converted to JSON.
  • Serializer: args render as $name; declarations prepended to the operation
    (query Op($x: Int!) {...}, also valid anonymously: query($x: Int)); body becomes
    {"query":"...","variables":{...}}. Values converted with a Jackson mapper copy using
    NON_NULL inclusion (enums → name, Input POJOs → Jackson, useObjectMapperForInputSerialization
    fields → JSON string). All-or-nothing fallback to inline if any non-null arg lacks a type.
  • Batch (GraphQLRequests): merge declarations; dedupe identical name+type, rename conflicts
    ($name__2).

Nested field arguments in response projections

Fields like type Client { data(ID: Int!): Data! } currently inline 5 into
query clients { data(ID: 5) {...} } — same problem, one level down.

  • GraphQLResponseProjection / GraphQLResponseField carry no arg-type info today;
    values only appear via ParametrizedInput.toString() (GraphQL literal syntax).
  • Generated projections must expose per-field arg name → GraphQL type.
  • Serializer walks the whole projection tree at serialization time, hoists every nested
    arg into a uniquely-named variable (same field with different args appears multiple
    times), and merges all declarations into the operation.
  • This roughly doubles the change surface vs. top-level-only, but doing it halfway would
    leave a confusing mode where some args are variables and others are literals.

Tests

  • GraphQLRequestSerializerTest: variables path, null skipping, enums/lists/nested inputs/
    custom scalars, anonymous ops, batch merge + conflict rename, fallback.
  • GraphQLCodegenRequestTest: type map present by default / absent with option off;
    regenerate affected expected-classes/request/* (Java + Kotlin).

Docs

  • README codegen-options table row for serializeRequestInputAsVariables.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions