Skip to content

Commit 5af3640

Browse files
authored
feat: make hybrid classes more flexible by default (#70)
1 parent 1e7a97e commit 5af3640

File tree

8 files changed

+78
-24
lines changed

8 files changed

+78
-24
lines changed

src/definitions/object.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,13 @@ export function buildObjectTypeDefinition(
6969
node.fields?.some((fieldNode) => fieldNode.arguments?.length),
7070
);
7171
if (shouldGenerateFunctions) {
72-
const fieldsWithNoArguments = node.fields?.filter(
72+
const atLeastOneFieldHasNoArguments = node.fields?.some(
7373
(fieldNode) => !fieldNode.arguments?.length,
7474
);
7575
const constructor =
76-
!typeInResolverInterfacesConfig && fieldsWithNoArguments?.length
77-
? `(\n${fieldsWithNoArguments
78-
.map((fieldNode) => {
76+
!typeInResolverInterfacesConfig && atLeastOneFieldHasNoArguments
77+
? `(\n${node.fields
78+
?.map((fieldNode) => {
7979
const typeMetadata = buildTypeMetadata(
8080
fieldNode.type,
8181
schema,
@@ -87,6 +87,8 @@ export function buildObjectTypeDefinition(
8787
schema,
8888
config,
8989
typeMetadata,
90+
shouldGenerateFunctions,
91+
true,
9092
);
9193
})
9294
.join(",\n")}\n)`

src/helpers/build-field-definition.ts

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,18 @@ export function buildFieldDefinition(
3232
config: CodegenConfigWithDefaults,
3333
typeMetadata: TypeMetadata,
3434
shouldGenerateFunctions?: boolean,
35+
isConstructorField?: boolean,
3536
) {
36-
const modifier = buildFieldModifier(node, fieldNode, schema, config);
37-
const fieldArguments = buildFieldArguments(node, fieldNode, schema, config);
37+
const modifier = buildFieldModifier(
38+
node,
39+
fieldNode,
40+
schema,
41+
config,
42+
isConstructorField,
43+
);
44+
const fieldArguments = isConstructorField
45+
? ""
46+
: buildFieldArguments(node, fieldNode, schema, config);
3847
const fieldDefinition = `${modifier} ${fieldNode.name.value}${fieldArguments}`;
3948
const annotations = buildAnnotations({
4049
config,
@@ -49,19 +58,28 @@ export function buildFieldDefinition(
4958
);
5059
}
5160

52-
const notImplementedError = ` = throw NotImplementedError("${node.name.value}.${fieldNode.name.value} must be implemented.")`;
53-
const defaultFunctionValue = `${typeMetadata.isNullable ? "?" : ""}${notImplementedError}`;
54-
const defaultValue = shouldGenerateFunctions
55-
? defaultFunctionValue
56-
: typeMetadata.defaultValue;
57-
const defaultDefinition = `${typeMetadata.typeName}${defaultValue}`;
61+
const notImplementedError = `throw NotImplementedError("${node.name.value}.${fieldNode.name.value} must be implemented.")`;
62+
const atLeastOneFieldHasNoArguments = node.fields?.some(
63+
(fieldNode) => !fieldNode.arguments?.length,
64+
);
5865
const typeInResolverInterfacesConfig = findTypeInResolverInterfacesConfig(
5966
node,
6067
config,
6168
);
69+
const defaultImplementation =
70+
!typeInResolverInterfacesConfig && atLeastOneFieldHasNoArguments
71+
? fieldNode.name.value
72+
: notImplementedError;
73+
const defaultFunctionValue = `${typeMetadata.isNullable ? "?" : ""} = ${defaultImplementation}`;
74+
const defaultValue =
75+
shouldGenerateFunctions && !isConstructorField
76+
? defaultFunctionValue
77+
: typeMetadata.defaultValue;
78+
const defaultDefinition = `${typeMetadata.typeName}${defaultValue}`;
79+
6280
const isCompletableFuture =
6381
typeInResolverInterfacesConfig?.classMethods === "COMPLETABLE_FUTURE";
64-
const completableFutureDefinition = `java.util.concurrent.CompletableFuture<${typeMetadata.typeName}${typeMetadata.isNullable ? "?" : ""}>${notImplementedError}`;
82+
const completableFutureDefinition = `java.util.concurrent.CompletableFuture<${typeMetadata.typeName}${typeMetadata.isNullable ? "?" : ""}> = ${defaultImplementation}`;
6583
const field = indent(
6684
`${fieldDefinition}: ${isCompletableFuture ? completableFutureDefinition : defaultDefinition}`,
6785
2,
@@ -74,6 +92,7 @@ function buildFieldModifier(
7492
fieldNode: FieldDefinitionNode,
7593
schema: GraphQLSchema,
7694
config: CodegenConfigWithDefaults,
95+
isConstructorField?: boolean,
7796
) {
7897
const typeInResolverInterfacesConfig = findTypeInResolverInterfacesConfig(
7998
node,
@@ -84,6 +103,10 @@ function buildFieldModifier(
84103
fieldNode,
85104
schema,
86105
);
106+
107+
if (isConstructorField && fieldNode.arguments?.length) {
108+
return "private val";
109+
}
87110
if (!typeInResolverInterfacesConfig && !fieldNode.arguments?.length) {
88111
return shouldOverrideField ? "override val" : "val";
89112
}

test/integration/Query.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,10 @@ import graphql.schema.DataFetchingEnvironment
55
import test.integration.Query as QueryInterface
66

77
class IntegrationTestQuery() : Query, QueryInterface() {
8-
override fun testQuery(dataFetchingEnvironment: DataFetchingEnvironment): SomeType = SomeType()
8+
override fun testQuery1(dataFetchingEnvironment: DataFetchingEnvironment): SomeType = SomeType()
9+
override fun testQuery2(dataFetchingEnvironment: DataFetchingEnvironment): SomeHybridType =
10+
SomeHybridType(
11+
someField = "test",
12+
someField2 = "test"
13+
)
914
}

test/integration/expected.graphql

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,13 @@ directive @specifiedBy(
3030
) on SCALAR
3131

3232
type Query {
33-
testQuery: SomeType!
33+
testQuery1: SomeType!
34+
testQuery2: SomeHybridType!
35+
}
36+
37+
type SomeHybridType {
38+
someField: String!
39+
someField2(input: String!): String
3440
}
3541

3642
type SomeType {

test/unit/should_generate_classes_for_types_with_field_args/expected.kt

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ open class TypeWithOnlyFieldArgs {
1111
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
1212
open class HybridType(
1313
val nullableField: String? = null,
14-
val nonNullableField: String
14+
val nonNullableField: String,
15+
private val nullableResolver: String? = null,
16+
private val nonNullableResolver: String
1517
) {
16-
open fun nullableResolver(arg: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String? = throw NotImplementedError("HybridType.nullableResolver must be implemented.")
17-
open fun nonNullableResolver(arg: InputTypeForResolver, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String = throw NotImplementedError("HybridType.nonNullableResolver must be implemented.")
18+
open fun nullableResolver(arg: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String? = nullableResolver
19+
open fun nonNullableResolver(arg: InputTypeForResolver, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String = nonNullableResolver
1820
}
1921

2022
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.INPUT_OBJECT])
@@ -36,8 +38,10 @@ open class TypeImplementingInterface(
3638
val booleanField1: Boolean? = null,
3739
val booleanField2: Boolean = false,
3840
val integerField1: Int? = null,
39-
val integerField2: Int
41+
val integerField2: Int,
42+
private val nullableListResolver: List<String?>? = null,
43+
private val nonNullableListResolver: List<String> = emptyList()
4044
) : HybridInterface {
41-
override fun nullableListResolver(arg1: Int?, arg2: Int, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): List<String?>? = throw NotImplementedError("TypeImplementingInterface.nullableListResolver must be implemented.")
42-
override fun nonNullableListResolver(arg1: Int, arg2: Int?, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): List<String> = throw NotImplementedError("TypeImplementingInterface.nonNullableListResolver must be implemented.")
45+
override fun nullableListResolver(arg1: Int?, arg2: Int, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): List<String?>? = nullableListResolver
46+
override fun nonNullableListResolver(arg1: Int, arg2: Int?, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): List<String> = nonNullableListResolver
4347
}

test/unit/should_honor_resolverInterfaces_config/expected.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,22 @@ import com.expediagroup.graphql.generator.annotations.*
44

55
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
66
open class MyIncludedResolverType {
7-
open fun nullableField(dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String? = throw NotImplementedError("MyIncludedResolverType.nullableField must be implemented.")
7+
open fun nullableField(dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): MyChildType? = throw NotImplementedError("MyIncludedResolverType.nullableField must be implemented.")
88
open fun nonNullableField(dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String = throw NotImplementedError("MyIncludedResolverType.nonNullableField must be implemented.")
99
open fun nullableResolver(arg: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String? = throw NotImplementedError("MyIncludedResolverType.nullableResolver must be implemented.")
1010
open fun nonNullableResolver(arg: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String = throw NotImplementedError("MyIncludedResolverType.nonNullableResolver must be implemented.")
1111
open fun nullableListResolver(arg1: Int? = null, arg2: Int, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): List<String?>? = throw NotImplementedError("MyIncludedResolverType.nullableListResolver must be implemented.")
1212
open fun nonNullableListResolver(arg1: Int, arg2: Int? = null, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): List<String> = throw NotImplementedError("MyIncludedResolverType.nonNullableListResolver must be implemented.")
1313
}
1414

15+
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
16+
open class MyChildType(
17+
val field: String? = null,
18+
private val field2: String? = null
19+
) {
20+
open fun field2(arg: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String? = field2
21+
}
22+
1523
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
1624
open class MyIncludedResolverTypeWithNoFieldArgs {
1725
open fun nullableField(dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String? = throw NotImplementedError("MyIncludedResolverTypeWithNoFieldArgs.nullableField must be implemented.")

test/unit/should_honor_resolverInterfaces_config/schema.graphql

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
type MyIncludedResolverType {
2-
nullableField: String
2+
nullableField: MyChildType
33
nonNullableField: String!
44
nullableResolver(arg: String!): String
55
nonNullableResolver(arg: String!): String!
66
nullableListResolver(arg1: Int, arg2: Int!): [String]
77
nonNullableListResolver(arg1: Int!, arg2: Int): [String!]!
88
}
99

10+
type MyChildType {
11+
field: String
12+
field2(arg: String!): String
13+
}
14+
1015
type MyIncludedResolverTypeWithNoFieldArgs {
1116
nullableField: String
1217
nonNullableField: String!

test/unit/should_replace_federation_directives/expected.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ data class FederatedType(
1414
@com.expediagroup.graphql.generator.federation.directives.KeyDirective(com.expediagroup.graphql.generator.federation.directives.FieldSet("some other field"))
1515
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
1616
open class FederatedTypeResolver(
17+
private val field: String,
1718
@com.expediagroup.graphql.generator.federation.directives.ExternalDirective
1819
val field2: String? = null
1920
) {
20-
open fun field(arg: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String = throw NotImplementedError("FederatedTypeResolver.field must be implemented.")
21+
open fun field(arg: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String = field
2122
}

0 commit comments

Comments
 (0)