Skip to content

Reject fields, values and members that a type and its extensions declare twice - #916

Merged
oryan-block merged 4 commits into
masterfrom
feature/863-reject-repeated-extensions
Oct 24, 2026
Merged

oryan-block merged 4 commits into
masterfrom
feature/863-reject-repeated-extensions

Conversation

@oryan-block

@oryan-block oryan-block commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #863
Fixes #865

Checklist

  • Pull requests follows the contribution guide
  • New or modified functionality is covered by tests

Description

Since #855 extensions are merged into the type they extend, and a repeat across the two was handled differently per kind. An enum value, interface field or union member declared twice kept the first one and logged a warning, so this builds Node with id: ID and only a log line says the extension's id was dropped:

interface Node { id: ID }
extend interface Node { id: String }

An object or input type that declared a field twice kept the last one, with a WARN since #920, and input objects listed the fields of their extensions before their own. graphql-java rejects the repeats and lists the type's fields first. These schemas built before #855 because the extension replaced the type, so they had to wait for 15.0.0.

Two commits, one per issue:

The check looks at the type and its extensions together, so a repeat inside one definition fails too (enum Color { RED RED }, type Query { a: String a: Int }). For enums, interfaces and unions the warning already covered that. For objects and input objects #920 added the warning, and throwing is what graphql-java does (has declared a field with a non unique name).

On graphql-java 26.1 and 25.1 I ran the same repeats through SchemaGenerator to see what to match. It rejects a repeated input field too, in SchemaTypeChecker before it generates anything, so the hasField check in buildInputObjectType that keeps the first one never gets a repeat. One difference: 25.1 doesn't flag a union member repeated between the type and an extension, 26.1 does. We reject it on both.

In TypeExtensionTest the test for extensions without a type no longer repeats anything, and there's a test for the errors (enum, interface, union, object, input, plus repeats between two extensions and inside one definition) and one for the input field order. Two of #920's warning cases became error cases here, a repeat in an input reached through another input and one in a definition without a source location. Filter in the test got limit and offset so the order test can query it, and inner for the nested input.

Verified with mvn clean verify on graphql-java 26.1 and with -Dgraphql-java.version=25.1, 412 tests each, no failures. After merging master (which dropped graphql-java 25 in #919 and added #920), mvn clean verify on 26.1 runs 415 tests, no failures. verify-tools on master before #920 and on this branch before the merge:

  • type-extensions: master exits 0 with Type 'Node' declares field 'id' more than once, only the first one is kept., the branch exits 2 with Type 'Node' declares field 'id' more than once at line 19, column 5 in scenarios/type-extensions/schema.graphqls.
  • a Query with greeting in the type and again in extend type Query: master builds with the extension's description, the branch exits 2 with Type 'Query' declares field 'greeting' more than once at line 7, column 5.
  • input Filter with extensions before and after it: getFieldDefinitions() gives [limit, offset, term] on master and [term, limit, offset] on the branch.
  • type-extensions-sealed builds the same on both.

Release note: a type and its extensions, or a single object or input definition, can't declare the same enum value, interface field, union member, object field or input field twice anymore. Before, enums, interfaces and unions kept the first one and objects and input objects kept the last one, each with a WARN. Now the build throws a SchemaError like Type 'Color' declares value 'RED' more than once at line 6, column 5. Remove the repeat from the type or the extension, since an extension only adds what the type doesn't have. Input objects also list the fields of the type first, then those of each extension in order, where the extensions' fields used to come first, so code that reads GraphQLInputObjectType.getFieldDefinitions() by position sees the new order.

…clares twice

A type that declared an enum value, interface field or union member twice, in the
type and again in an extension, or twice in extensions, kept the first one and
logged a warning. That hides mistakes: `interface Node { id: ID }` plus
`extend interface Node { id: String }` built with `id: ID`, and only a log line
said the extension's `id` was dropped. graphql-java's SchemaGenerator rejects
these schemas.

The build now throws a SchemaError that names the type, the repeated value, field
or member, and where it is repeated. The test for extensions without a type no
longer repeats anything, and a new test covers the errors.

Fixes #863
…elds like graphql-java

An object type that declared a field in the type and again in an extension kept the
last one, so `greeting` took the description of the extension's declaration. Input
objects listed the fields of their extensions before their own, so
`input Filter { term: String }` plus `extend input Filter { limit: Int }` gave
`limit, term`. graphql-java rejects the repeated field and lists the fields of the
type first, then those of each extension in order.

Object and input fields now go through the same check as enum values, interface
fields and union members. Input objects list the fields of the type first.

Fixes #865
…repeated-extensions

# Conflicts:
#	src/main/kotlin/graphql/kickstart/tools/SchemaParser.kt
#	src/test/kotlin/graphql/kickstart/tools/TypeExtensionTest.kt
@sonarqubecloud

Copy link
Copy Markdown

@oryan-block
oryan-block merged commit 54f5d62 into master Oct 24, 2026
6 checks passed
@oryan-block
oryan-block deleted the feature/863-reject-repeated-extensions branch October 24, 2026 00:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant