Skip to content

Commit a728c6f

Browse files
committed
feat(codegen): refactor directives
1 parent 3fa9dcb commit a728c6f

File tree

16 files changed

+337
-80
lines changed

16 files changed

+337
-80
lines changed

graphql-kotlin-toolkit-codegen/src/main/kotlin/com/auritylab/graphql/kotlin/toolkit/codegen/Codegen.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.auritylab.graphql.kotlin.toolkit.codegen
22

33
import com.auritylab.graphql.kotlin.toolkit.codegen.codeblock.ArgumentCodeBlockGenerator
4+
import com.auritylab.graphql.kotlin.toolkit.codegen.directive.DirectiveFacade
45
import com.auritylab.graphql.kotlin.toolkit.codegen.generator.GeneratorFactory
56
import com.auritylab.graphql.kotlin.toolkit.codegen.mapper.GeneratedMapper
67
import com.auritylab.graphql.kotlin.toolkit.codegen.mapper.ImplementerMapper
@@ -27,6 +28,9 @@ class Codegen(
2728
* Will generate code for the types of the [schema].
2829
*/
2930
fun generate() {
31+
// Validate the directives.
32+
DirectiveFacade.validateAllOnSchema(schema)
33+
3034
// Build the generators using the CodegenController.
3135
CodegenController(options, schema.allTypesAsList, generatorFactory)
3236
.buildGenerators()

graphql-kotlin-toolkit-codegen/src/main/kotlin/com/auritylab/graphql/kotlin/toolkit/codegen/CodegenController.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package com.auritylab.graphql.kotlin.toolkit.codegen
22

3+
import com.auritylab.graphql.kotlin.toolkit.codegen.directive.DirectiveFacade
34
import com.auritylab.graphql.kotlin.toolkit.codegen.generator.FileGenerator
45
import com.auritylab.graphql.kotlin.toolkit.codegen.generator.GeneratorFactory
5-
import com.auritylab.graphql.kotlin.toolkit.codegen.helper.DirectiveHelper
66
import graphql.schema.GraphQLEnumType
77
import graphql.schema.GraphQLInputObjectType
88
import graphql.schema.GraphQLInterfaceType
@@ -52,9 +52,9 @@ internal class CodegenController(
5252
.flatMap { objectType ->
5353
val internalGenerators = mutableListOf<FileGenerator>()
5454

55-
val objectHasGenerate = DirectiveHelper.hasGenerate(objectType)
56-
val objectHasRepresentation = DirectiveHelper.getRepresentationClass(objectType) != null
57-
val objectHasResolver = DirectiveHelper.hasResolver(objectType)
55+
val objectHasGenerate = DirectiveFacade.generate[objectType]
56+
val objectHasRepresentation = DirectiveFacade.representation[objectType]
57+
val objectHasResolver = DirectiveFacade.resolver[objectType]
5858

5959
// Generate for object type if the directive is given and does not have a representation class.
6060
if ((options.generateAll && !objectHasRepresentation) || (objectHasGenerate && !objectHasRepresentation))
@@ -63,7 +63,7 @@ internal class CodegenController(
6363
objectType.fieldDefinitions.forEach { fieldDefinition ->
6464
if (options.generateAll ||
6565
objectHasResolver ||
66-
DirectiveHelper.hasResolver(fieldDefinition)
66+
DirectiveFacade.resolver[fieldDefinition]
6767
) internalGenerators.add(generatorFactory.fieldResolver(objectType, fieldDefinition))
6868
}
6969

@@ -78,12 +78,12 @@ internal class CodegenController(
7878
.flatMap { interfaceType ->
7979
val internalGenerators = mutableListOf<FileGenerator>()
8080

81-
val generatedForInterface = DirectiveHelper.hasResolver(interfaceType)
81+
val generatedForInterface = DirectiveFacade.resolver[interfaceType]
8282

8383
interfaceType.fieldDefinitions.forEach { fieldDefinition ->
8484
if (options.generateAll ||
8585
generatedForInterface ||
86-
DirectiveHelper.hasResolver(fieldDefinition)
86+
DirectiveFacade.resolver[fieldDefinition]
8787
) internalGenerators.add(generatorFactory.fieldResolver(interfaceType, fieldDefinition))
8888
}
8989

graphql-kotlin-toolkit-codegen/src/main/kotlin/com/auritylab/graphql/kotlin/toolkit/codegen/codeblock/ArgumentCodeBlockGenerator.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.auritylab.graphql.kotlin.toolkit.codegen.codeblock
22

3-
import com.auritylab.graphql.kotlin.toolkit.codegen.helper.DirectiveHelper
3+
import com.auritylab.graphql.kotlin.toolkit.codegen.directive.DirectiveFacade
44
import com.auritylab.graphql.kotlin.toolkit.codegen.helper.NamingHelper
55
import com.auritylab.graphql.kotlin.toolkit.codegen.mapper.GeneratedMapper
66
import com.auritylab.graphql.kotlin.toolkit.codegen.mapper.KotlinTypeMapper
@@ -71,7 +71,7 @@ internal class ArgumentCodeBlockGenerator(
7171

7272
val lastLayerIndex = currentIndex - 1
7373

74-
if (lastType.isNullable && DirectiveHelper.hasDoubleNull(fieldDirectiveContainer))
74+
if (lastType.isNullable && DirectiveFacade.doubleNull[fieldDirectiveContainer])
7575
code.addStatement(
7676
"return if (map.containsKey(\"%L\")) %T(layer%L(map[\"%L\"] as %T)) else null",
7777
name,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.auritylab.graphql.kotlin.toolkit.codegen.directive
2+
3+
import com.auritylab.graphql.kotlin.toolkit.codegen.directive.exception.DirectiveValidationException
4+
import graphql.introspection.Introspection
5+
import graphql.schema.GraphQLDirective
6+
import graphql.schema.GraphQLType
7+
8+
abstract class AbstractDirective(
9+
override val name: String,
10+
override val required: Boolean
11+
) : Directive {
12+
/**
13+
* Will check if the given [directive] will match against the reference ([reference]).
14+
*/
15+
override fun validateDefinition(directive: GraphQLDirective) {
16+
// Validate the arguments of the directive.
17+
reference.arguments.forEach {
18+
val argOfDirective = directive.getArgument(it.name)
19+
20+
// Check if the reference argument exists on the given directive
21+
@Suppress("FoldInitializerAndIfToElvis")
22+
if (argOfDirective == null)
23+
throw buildArgumentNotFoundException(it.name)
24+
25+
// Check if the type of the reference argument is the same as on the given directive.
26+
if (argOfDirective.type != it.type)
27+
throw buildInvalidArgumentTypeException(it.name, it.type)
28+
}
29+
30+
val locationsOfDirective = directive.validLocations()
31+
reference.validLocations().forEach {
32+
// Check if the location from the reference exists on the directive.
33+
if (!locationsOfDirective.contains(it))
34+
throw buildInvalidLocationException(it)
35+
}
36+
}
37+
38+
/**
39+
* Will build a [DirectiveValidationException] which tells that the argument with the given [name] could not be
40+
* found on the directive definition.
41+
*/
42+
protected fun buildArgumentNotFoundException(
43+
name: String
44+
): DirectiveValidationException =
45+
DirectiveValidationException(this, "Argument '$name' not found on directive")
46+
47+
/**
48+
* Will build a [DirectiveValidationException] which tells that the argument with the given [argumentName] was
49+
* defined with the wrong type. The [expectedType] tells the expected type for the argument.
50+
*/
51+
protected fun buildInvalidArgumentTypeException(
52+
argumentName: String,
53+
expectedType: GraphQLType
54+
): DirectiveValidationException =
55+
DirectiveValidationException(
56+
this,
57+
"Argument '$argumentName' is expected to be type of '${expectedType.name}'"
58+
)
59+
60+
/**
61+
* Will build a [DirectiveValidationException] which tells directive requires the given [requiredLocation].
62+
*/
63+
protected fun buildInvalidLocationException(
64+
requiredLocation: Introspection.DirectiveLocation
65+
): DirectiveValidationException =
66+
DirectiveValidationException(this, "Directive location '${requiredLocation.name}' not found on directive")
67+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.auritylab.graphql.kotlin.toolkit.codegen.directive
2+
3+
import com.auritylab.graphql.kotlin.toolkit.codegen.directive.exception.DirectiveValidationException
4+
import graphql.schema.GraphQLDirective
5+
import graphql.schema.GraphQLDirectiveContainer
6+
7+
/**
8+
* Describes a directive which is used by the code generator.
9+
*/
10+
interface Directive {
11+
/**
12+
* The name of the directive.
13+
*/
14+
val name: String
15+
16+
/**
17+
* If the directive MUST be present on the schema.
18+
*/
19+
val required: Boolean
20+
21+
/**
22+
* The reference [GraphQLDirective], which is represented here.
23+
*/
24+
val reference: GraphQLDirective
25+
26+
/**
27+
* Will validate the definition of given [directive]. This has to validate if all arguments are defined with
28+
* the correct scalar, etc.
29+
*
30+
* @param directive The directive definition to validate against.
31+
* @throws DirectiveValidationException If the validation was not successful.
32+
*/
33+
fun validateDefinition(directive: GraphQLDirective)
34+
35+
/**
36+
* Will check if this directive exists on the given [container].
37+
*
38+
* @param container The container to check against.
39+
* @return If this directive exists on the given [container].
40+
*/
41+
fun existsOnContainer(container: GraphQLDirectiveContainer): Boolean =
42+
container.getDirective(name) != null
43+
44+
/**
45+
* @see existsOnContainer
46+
*/
47+
operator fun get(container: GraphQLDirectiveContainer): Boolean =
48+
existsOnContainer(container)
49+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.auritylab.graphql.kotlin.toolkit.codegen.directive
2+
3+
import com.auritylab.graphql.kotlin.toolkit.codegen.directive.exception.DirectiveValidationException
4+
import com.auritylab.graphql.kotlin.toolkit.codegen.directive.implementation.DoubleNullDirective
5+
import com.auritylab.graphql.kotlin.toolkit.codegen.directive.implementation.ErrorDirective
6+
import com.auritylab.graphql.kotlin.toolkit.codegen.directive.implementation.GenerateDirective
7+
import com.auritylab.graphql.kotlin.toolkit.codegen.directive.implementation.RepresentationDirective
8+
import com.auritylab.graphql.kotlin.toolkit.codegen.directive.implementation.ResolverDirective
9+
import graphql.schema.GraphQLDirective
10+
import graphql.schema.GraphQLSchema
11+
12+
object DirectiveFacade {
13+
val generate = GenerateDirective()
14+
val resolver = ResolverDirective()
15+
val representation = RepresentationDirective()
16+
val doubleNull = DoubleNullDirective()
17+
val error = ErrorDirective()
18+
19+
// List of all CodegenDirectives
20+
private val directivesList = listOf<Directive>(generate, resolver, representation, doubleNull, error)
21+
22+
/**
23+
* Will validate all existing [Directive]s on the given [schema].
24+
*
25+
* @throws DirectiveValidationException If the validation of any directive fails.
26+
*/
27+
fun validateAllOnSchema(schema: GraphQLSchema) {
28+
directivesList.forEach { codegenDirective ->
29+
getDirectiveDefinition(codegenDirective, schema)
30+
?.let { codegenDirective.validateDefinition(it) }
31+
}
32+
}
33+
34+
/**
35+
* Will check if the given [directive] exists on the given [schema] (using [Directive.name]).
36+
* Additional it will return the [GraphQLDirective] if it available on the schema
37+
*
38+
* @throws DirectiveValidationException If the directive is required but is not present on the [schema].
39+
*/
40+
private fun getDirectiveDefinition(directive: Directive, schema: GraphQLSchema): GraphQLDirective? {
41+
val schemaDirective = schema.getDirective(directive.name)
42+
43+
// Throw exception if the schema is required but not given on the schema.
44+
if (schemaDirective == null && directive.required)
45+
throw DirectiveValidationException(
46+
directive,
47+
"Not present on the schema, but is required. Consider adding it."
48+
)
49+
50+
return schemaDirective
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.auritylab.graphql.kotlin.toolkit.codegen.directive
2+
3+
import graphql.schema.GraphQLDirective
4+
import graphql.schema.GraphQLDirectiveContainer
5+
6+
/**
7+
* Describes a [Directive], which has arguments.
8+
*/
9+
interface HasArgumentsDirective<M : Any> : Directive {
10+
/**
11+
* Will resolve the given [directive] into an ínstance of [M], which is a representation of the arguments.
12+
*/
13+
fun getArguments(directive: GraphQLDirective): M
14+
15+
/**
16+
* Will resolve this directive in the given [container] into an instance of [M], which is a representation of the
17+
* arguments.
18+
*/
19+
fun getArguments(container: GraphQLDirectiveContainer): M?
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.auritylab.graphql.kotlin.toolkit.codegen.directive.exception
2+
3+
import com.auritylab.graphql.kotlin.toolkit.codegen.directive.Directive
4+
5+
/**
6+
* Represents a exception, which will be thrown when the directive was not found on a container.
7+
*/
8+
class DirectiveNotFoundException(directive: Directive, message: String) :
9+
Exception("Directive '${directive.name}': $message")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.auritylab.graphql.kotlin.toolkit.codegen.directive.exception
2+
3+
import com.auritylab.graphql.kotlin.toolkit.codegen.directive.Directive
4+
5+
/**
6+
* Represents a exception, which will be thrown if the directive validation was not successful
7+
*
8+
* @see Directive.validateDefinition
9+
*/
10+
class DirectiveValidationException(
11+
directive: Directive,
12+
message: String
13+
) : Exception("Directive '${directive.name}': $message")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.auritylab.graphql.kotlin.toolkit.codegen.directive.implementation
2+
3+
import com.auritylab.graphql.kotlin.toolkit.codegen.directive.AbstractDirective
4+
import graphql.introspection.Introspection
5+
import graphql.schema.GraphQLDirective
6+
7+
class DoubleNullDirective : AbstractDirective("kDoubleNull", false) {
8+
override val reference: GraphQLDirective =
9+
GraphQLDirective.newDirective()
10+
.name(name)
11+
.validLocations(
12+
Introspection.DirectiveLocation.INPUT_FIELD_DEFINITION,
13+
Introspection.DirectiveLocation.ARGUMENT_DEFINITION
14+
)
15+
.build()
16+
}

0 commit comments

Comments
 (0)