Skip to content

Commit 0ec2017

Browse files
committed
fix: include GraphQLValidObject annotation on all input and output types
1 parent c6f38ab commit 0ec2017

File tree

28 files changed

+124
-50
lines changed

28 files changed

+124
-50
lines changed

src/definitions/input.ts

Lines changed: 7 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,13 @@ See the License for the specific language governing permissions and
1111
limitations under the License.
1212
*/
1313

14-
import {
15-
GraphQLSchema,
16-
InputObjectTypeDefinitionNode,
17-
Kind,
18-
TypeNode,
19-
} from "graphql";
14+
import { GraphQLSchema, InputObjectTypeDefinitionNode } from "graphql";
2015
import { shouldIncludeTypeDefinition } from "../helpers/should-include-type-definition";
2116
import { buildTypeMetadata } from "../helpers/build-type-metadata";
2217
import { buildAnnotations } from "../helpers/build-annotations";
2318
import { indent } from "@graphql-codegen/visitor-plugin-common";
2419
import { CodegenConfigWithDefaults } from "../helpers/build-config-with-defaults";
20+
import { inputTypeHasMatchingOutputType } from "../helpers/input-type-has-matching-output-type";
2521

2622
export function buildInputObjectDefinition(
2723
node: InputObjectTypeDefinitionNode,
@@ -32,21 +28,8 @@ export function buildInputObjectDefinition(
3228
return "";
3329
}
3430

35-
const typeNameWithoutInput = getTypeNameWithoutInput(node.name.value);
36-
const matchingType = schema.getType(typeNameWithoutInput)?.astNode;
37-
const matchingTypeFields =
38-
matchingType?.kind === Kind.OBJECT_TYPE_DEFINITION
39-
? matchingType.fields
40-
: [];
41-
const inputFields = node.fields;
42-
const fieldsMatch = matchingTypeFields?.every((field) => {
43-
const matchingInputField = inputFields?.find(
44-
(inputField) => inputField.name.value === field.name.value,
45-
);
46-
if (!matchingInputField) return false;
47-
return fieldsAreEquivalent(field.type, matchingInputField.type);
48-
});
49-
if (matchingTypeFields?.length && fieldsMatch) {
31+
const typeWillBeConsolidated = inputTypeHasMatchingOutputType(node, schema);
32+
if (typeWillBeConsolidated) {
5033
return "";
5134
}
5235

@@ -73,34 +56,9 @@ export function buildInputObjectDefinition(
7356
definitionNode: node,
7457
});
7558

76-
return `${annotations}data class ${node.name.value}(
59+
const inputRestrictionAnnotation =
60+
"@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.INPUT_OBJECT])\n";
61+
return `${annotations}${inputRestrictionAnnotation}data class ${node.name.value}(
7762
${classMembers}
7863
)`;
7964
}
80-
81-
function getTypeNameWithoutInput(name: string) {
82-
return name.endsWith("Input") ? name.replace("Input", "") : name;
83-
}
84-
85-
function fieldsAreEquivalent(
86-
typeField: TypeNode,
87-
inputField: TypeNode,
88-
): boolean {
89-
switch (typeField.kind) {
90-
case Kind.NAMED_TYPE:
91-
return (
92-
inputField.kind === Kind.NAMED_TYPE &&
93-
typeField.name.value === getTypeNameWithoutInput(inputField.name.value)
94-
);
95-
case Kind.LIST_TYPE:
96-
return (
97-
inputField.kind === Kind.LIST_TYPE &&
98-
fieldsAreEquivalent(typeField.type, inputField.type)
99-
);
100-
case Kind.NON_NULL_TYPE:
101-
return (
102-
inputField.kind === Kind.NON_NULL_TYPE &&
103-
fieldsAreEquivalent(typeField.type, inputField.type)
104-
);
105-
}
106-
}

src/definitions/object.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { isResolverType } from "../helpers/is-resolver-type";
2424
import { buildFieldDefinition } from "../helpers/build-field-definition";
2525
import { isExternalField } from "../helpers/is-external-field";
2626
import { CodegenConfigWithDefaults } from "../helpers/build-config-with-defaults";
27+
import { inputTypeHasMatchingOutputType } from "../helpers/input-type-has-matching-output-type";
2728

2829
export function buildObjectTypeDefinition(
2930
node: ObjectTypeDefinitionNode,
@@ -57,7 +58,14 @@ ${getDataClassMembers({ node, schema, config, completableFuture: true })}
5758
}`;
5859
}
5960

60-
return `${annotations}data class ${name}(
61+
const potentialMatchingInputType = schema.getType(`${name}Input`)?.astNode;
62+
const typeWillBeConsolidated =
63+
potentialMatchingInputType?.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION &&
64+
inputTypeHasMatchingOutputType(potentialMatchingInputType, schema);
65+
const outputRestrictionAnnotation = typeWillBeConsolidated
66+
? ""
67+
: "@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])\n";
68+
return `${annotations}${outputRestrictionAnnotation}data class ${name}(
6169
${getDataClassMembers({ node, schema, config })}
6270
)${interfaceInheritance}`;
6371
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { Kind, TypeNode } from "graphql/index";
2+
import { GraphQLSchema, InputObjectTypeDefinitionNode } from "graphql";
3+
4+
export function inputTypeHasMatchingOutputType(
5+
inputTypeNode: InputObjectTypeDefinitionNode,
6+
schema: GraphQLSchema,
7+
) {
8+
const typeNameWithoutInput = getTypeNameWithoutInput(
9+
inputTypeNode.name.value,
10+
);
11+
const matchingType = schema.getType(typeNameWithoutInput)?.astNode;
12+
const matchingTypeFields =
13+
matchingType?.kind === Kind.OBJECT_TYPE_DEFINITION
14+
? matchingType.fields
15+
: [];
16+
const inputFields = inputTypeNode.fields;
17+
const fieldsMatch = matchingTypeFields?.every((field) => {
18+
const matchingInputField = inputFields?.find(
19+
(inputField) => inputField.name.value === field.name.value,
20+
);
21+
if (!matchingInputField) return false;
22+
return fieldsAreEquivalent(field.type, matchingInputField.type);
23+
});
24+
return Boolean(matchingTypeFields?.length && fieldsMatch);
25+
}
26+
27+
function getTypeNameWithoutInput(name: string) {
28+
return name.endsWith("Input") ? name.replace("Input", "") : name;
29+
}
30+
31+
function fieldsAreEquivalent(
32+
typeField: TypeNode,
33+
inputField: TypeNode,
34+
): boolean {
35+
switch (typeField.kind) {
36+
case Kind.NAMED_TYPE:
37+
return (
38+
inputField.kind === Kind.NAMED_TYPE &&
39+
typeField.name.value === getTypeNameWithoutInput(inputField.name.value)
40+
);
41+
case Kind.LIST_TYPE:
42+
return (
43+
inputField.kind === Kind.LIST_TYPE &&
44+
fieldsAreEquivalent(typeField.type, inputField.type)
45+
);
46+
case Kind.NON_NULL_TYPE:
47+
return (
48+
inputField.kind === Kind.NON_NULL_TYPE &&
49+
fieldsAreEquivalent(typeField.type, inputField.type)
50+
);
51+
}
52+
}

test/unit/should_annotate_types_properly/expected.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.kotlin.generated
33
import com.expediagroup.graphql.generator.annotations.*
44

55
@GraphQLDescription("A description for MyType")
6+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
67
data class TypeThatShouldBeProperlyAnnotated(
78
val field: String? = null,
89
@GraphQLDescription("A description for fieldWithDescription")

test/unit/should_consolidate_input_and_output_types/expected.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,28 +25,34 @@ data class MyTypeToConsolidate4(
2525
val field: String? = null
2626
)
2727

28+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
2829
data class MyTypeNotToConsolidate(
2930
val field: String? = null
3031
)
3132

3233
@GraphQLDescription("The type name must exactly match in order to consolidate")
34+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.INPUT_OBJECT])
3335
data class MyTypeToNotConsolidateInput(
3436
val field: String? = null
3537
)
3638

39+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
3740
data class MyTypeToNotConsolidate2(
3841
val field: String? = null
3942
)
4043

44+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.INPUT_OBJECT])
4145
data class MyTypeInputToNotConsolidate2(
4246
val field: String? = null
4347
)
4448

49+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
4550
data class MyTypeWhereFieldsDoNotMatch(
4651
val field: String? = null,
4752
val field2: String? = null
4853
)
4954

55+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.INPUT_OBJECT])
5056
data class MyTypeWhereFieldsDoNotMatchInput(
5157
val field: String? = null,
5258
val field2: Int? = null

test/unit/should_convert_graphql_float_to_kotlin_double/expected.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.kotlin.generated
22

33
import com.expediagroup.graphql.generator.annotations.*
44

5+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
56
data class MyFloatType(
67
val field: Double? = null
78
)

test/unit/should_default_non_nullable_boolean_fields_to_false/expected.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.kotlin.generated
22

33
import com.expediagroup.graphql.generator.annotations.*
44

5+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
56
data class MyBooleanType(
67
val field: Boolean = false,
78
val field2: Boolean? = null

test/unit/should_generate_field_resolver_interfaces/expected.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ interface QueryCompletableFuture {
1818
fun nonNullableResolver(arg: InputTypeGenerateFieldResolverInterfaces): java.util.concurrent.CompletableFuture<String>
1919
}
2020

21+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.INPUT_OBJECT])
2122
data class InputTypeGenerateFieldResolverInterfaces(
2223
val field: String? = null
2324
)

test/unit/should_generate_input_types_properly/expected.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.kotlin.generated
33
import com.expediagroup.graphql.generator.annotations.*
44

55
@GraphQLDescription("A description for MyInputType")
6+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.INPUT_OBJECT])
67
data class InputTypeThatShouldBeGeneratedProperly(
78
val field1: String? = null,
89
@GraphQLDescription("A description for field2")

test/unit/should_generate_interfaces_with_inheritance/expected.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ interface InterfaceWithInheritance {
99
}
1010

1111
@GraphQLDescription("A description for MyInterfaceImplementation")
12+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
1213
data class MyInterfaceImplementation(
1314
override val field: String? = null,
1415
override val field2: String
@@ -22,6 +23,7 @@ interface InheritedInterface2 {
2223
val field2: String
2324
}
2425

26+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
2527
data class MyMergedInterfaceImplementation(
2628
override val field: String? = null,
2729
override val field2: String

test/unit/should_generate_list_types_properly/expected.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.kotlin.generated
22

33
import com.expediagroup.graphql.generator.annotations.*
44

5+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
56
data class MyListType(
67
val field: List<String> = emptyList(),
78
val field2: List<String?> = emptyList(),

test/unit/should_generate_multi_union_types_properly/expected.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package com.kotlin.generated
22

33
import com.expediagroup.graphql.generator.annotations.*
44

5+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
56
data class MyType3(
67
val field: String? = null
78
) : MyUnion1, MyUnion2
89

10+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
911
data class MyType4(
1012
val field: String? = null
1113
) : MyUnion1, MyUnion2
@@ -14,6 +16,7 @@ interface MyUnion1
1416

1517
interface MyUnion2
1618

19+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
1720
data class MyMultiUnionType(
1821
val field: MyUnion1? = null,
1922
val field2: MyUnion2? = null

test/unit/should_generate_non_nullable_union_list_types_properly/expected.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@ package com.kotlin.generated
33
import com.expediagroup.graphql.generator.annotations.*
44

55
@GraphQLDescription("A description for MyType1")
6+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
67
data class TypeForNonNullableUnionList1(
78
val field: String? = null
89
) : UnionForNonNullableList
910

11+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
1012
data class TypeForNonNullableUnionList2(
1113
val field: String? = null
1214
) : UnionForNonNullableList
1315

1416
@GraphQLDescription("A description for UnionForNonNullableList")
1517
interface UnionForNonNullableList
1618

19+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
1720
data class MyNonNullableUnionListType(
1821
val field: List<UnionForNonNullableList> = emptyList(),
1922
val field2: List<UnionForNonNullableList?> = emptyList()

test/unit/should_generate_type_with_interface_field_type/expected.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ interface MyInterface {
88
val field2: String
99
}
1010

11+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
1112
data class MyInterfaceFieldType(
1213
val field: MyInterface? = null,
1314
val field2: MyInterface

test/unit/should_generate_union_list_types_properly/expected.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@ package com.kotlin.generated
33
import com.expediagroup.graphql.generator.annotations.*
44

55
@GraphQLDescription("A description for TypeForGeneratingUnionListTypes1")
6+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
67
data class TypeForGeneratingUnionListTypes1(
78
val field: String? = null
89
) : UnionForGeneratingUnionListTypes
910

11+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
1012
data class TypeForGeneratingUnionListTypes2(
1113
val field: String? = null
1214
) : UnionForGeneratingUnionListTypes
1315

1416
@GraphQLDescription("A description for UnionForGeneratingUnionListTypes")
1517
interface UnionForGeneratingUnionListTypes
1618

19+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
1720
data class MyUnionListType(
1821
@GraphQLDescription("A description for field")
1922
val field: List<UnionForGeneratingUnionListTypes>? = null,

test/unit/should_generate_union_types_properly/expected.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@ package com.kotlin.generated
33
import com.expediagroup.graphql.generator.annotations.*
44

55
@GraphQLDescription("A description for MyType1")
6+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
67
data class TypeForGeneratingUnionTypesProperly1(
78
val field: String? = null
89
) : UnionForGeneratingUnionsProperly
910

11+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
1012
data class TypeForGeneratingUnionTypesProperly2(
1113
val field: String? = null
1214
) : UnionForGeneratingUnionsProperly
1315

1416
@GraphQLDescription("A trimmed description for UnionForGeneratingUnionsProperly")
1517
interface UnionForGeneratingUnionsProperly
1618

19+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
1720
data class MyUnionType(
1821
@GraphQLDescription("A description for field")
1922
val field: UnionForGeneratingUnionsProperly? = null,

test/unit/should_honor_dependentTypesInScope_config/expected.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package com.kotlin.generated
33
import com.expediagroup.graphql.generator.annotations.*
44
import should_honor_dependentTypesInScope_config.*
55

6+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
67
data class MyTypeInOnlyTypes(
78
val field: TypeInScope,
89
val field2: TypeOutOfScope
910
)
1011

12+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
1113
data class TypeInScope(
1214
val field: String? = null,
1315
val unionInScopeField: UnionInScope? = null,
@@ -17,6 +19,7 @@ data class TypeInScope(
1719

1820
interface UnionInScope
1921

22+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
2023
data class Type1(
2124
val field: String? = null
2225
) : UnionInScope, ExternalUnionAsInterface

test/unit/should_honor_directiveReplacements_config/expected.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import should_honor_directiveReplacements_config.*
77
@SomeAnnotation1
88
@SomeAnnotation2
99
@SomeAnnotationWithArgs(arg1 = "arg1", arg2 = 0)
10+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
1011
data class TypeHonoringDirectiveReplacements(
1112
val field: String? = null
1213
) : MyDirectiveUnion

test/unit/should_honor_directiveReplacements_config_with_multiple_directives/expected.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import should_honor_directiveReplacements_config.*
66
@GraphQLDescription("A description for MyDirectiveType")
77
@SomeAnnotation1
88
@SomeAnnotation2
9+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
910
data class TypeHonoringDirectiveReplacementsMultipleDirectives(
1011
val field: String? = null
1112
)

0 commit comments

Comments
 (0)