Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 22 additions & 13 deletions src/main/kotlin/graphql/kickstart/tools/SchemaParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -380,19 +380,16 @@
}
}
.apply {
// a bare @deprecated has no "reason" argument, which makes SchemaPrinter throw a NPE.
// copy the default from the directive definition (for the built-in one: "No longer supported").
if (directive.name == Directives.DeprecatedDirective.name && directive.arguments.none { it.name == "reason" }) {
val reasonArgument = graphQLArguments["reason"]
if (reasonArgument != null && reasonArgument.hasSetDefaultValue()) {
argument(GraphQLAppliedDirectiveArgument.newArgument()
.name(reasonArgument.name)
.type(reasonArgument.type)
.description(reasonArgument.description)
.inputValueWithState(reasonArgument.argumentDefaultValue)
.build()
)
}
// arguments that weren't supplied get the default from the directive definition, like graphql-java does.
// this also gives a bare @deprecated its "reason", without which SchemaPrinter throws a NPE.
missingArgumentsWithDefault(directive, graphQLDirective).forEach { graphQLArgument ->
argument(GraphQLAppliedDirectiveArgument.newArgument()
.name(graphQLArgument.name)
.type(graphQLArgument.type)
.description(graphQLArgument.description)
.inputValueWithState(graphQLArgument.argumentDefaultValue)
.build()
)
}
}
.build()
Expand Down Expand Up @@ -465,6 +462,15 @@
.valueLiteral(arg.value)
.build())
}
missingArgumentsWithDefault(directive, graphQLDirective).forEach { graphQLArgument ->
val defaultValue = graphQLArgument.argumentDefaultValue
argument(GraphQLArgument.newArgument()
.name(graphQLArgument.name)
.type(graphQLArgument.type)
.description(graphQLArgument.description)
.apply { if (defaultValue.isLiteral) valueLiteral(defaultValue.value as Value<*>) else valueProgrammatic(defaultValue.value) }

Check warning on line 471 in src/main/kotlin/graphql/kickstart/tools/SchemaParser.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Deprecated code should not be used.

See more on https://sonarcloud.io/project/issues?id=graphql-java-kickstart_graphql-java-tools&issues=AaED6RsirLHh1NZRYogZ&open=AaED6RsirLHh1NZRYogZ&pullRequest=834

Check warning on line 471 in src/main/kotlin/graphql/kickstart/tools/SchemaParser.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Deprecated code should not be used.

See more on https://sonarcloud.io/project/issues?id=graphql-java-kickstart_graphql-java-tools&issues=AaED6RsirLHh1NZRYogY&open=AaED6RsirLHh1NZRYogY&pullRequest=834
.build())
}
}
.build()
)
Expand All @@ -474,6 +480,9 @@
return output.toTypedArray()
}

private fun missingArgumentsWithDefault(directive: Directive, graphQLDirective: GraphQLDirective): List<GraphQLArgument> =
graphQLDirective.arguments.filter { it.hasSetDefaultValue() && directive.getArgument(it.name) == null }

private fun determineOutputType(typeDefinition: Type<*>, inputObjects: List<GraphQLInputObjectType>) =
determineType(GraphQLOutputType::class, typeDefinition, permittedTypesForObject, inputObjects) as GraphQLOutputType

Expand Down
78 changes: 78 additions & 0 deletions src/test/kotlin/graphql/kickstart/tools/DirectiveTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,54 @@ class DirectiveTest {
assert((schema.getType("Book") as GraphQLObjectType).getField("name").isDeprecated)
}

@Test
fun `should fill in default values of directive arguments that weren't supplied`() {
val emailDirective = EmailDirective()
val schema = SchemaParser.newParser()
.schemaString(
"""
directive @email(message: String = "{path} must be a valid email") on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION
directive @owner(team: String = "books-team") on SCHEMA | ENUM_VALUE

schema @owner {
query: Query
}

enum AllowedState {
ALLOWED @owner
DISALLOWED
}

input PersonInput {
email: String @email
}

type Query {
contactEmail: String @email
updatePersonEmail(email: String @email, backupEmail: String @email(message: "invalid backup email")): String
updatePerson(person: PersonInput, state: AllowedState): String
}
""")
.resolvers(PersonQueryResolver())
.directive("email", emailDirective)
.build()
.makeExecutableSchema()

assertEquals(
emailDirective.messages,
mapOf(
"contactEmail" to ("{path} must be a valid email" to "{path} must be a valid email"),
"email" to ("{path} must be a valid email" to "{path} must be a valid email"),
"backupEmail" to ("invalid backup email" to "invalid backup email")
)
)
val inputField = (schema.getType("PersonInput") as GraphQLInputObjectType).getField("email")
assertEquals(inputField.getAppliedDirective("email").getArgument("message")?.getValue<String>(), "{path} must be a valid email")
assertEquals(schema.getSchemaAppliedDirective("owner").getArgument("team")?.getValue<String>(), "books-team")
val enumValue = (schema.getType("AllowedState") as GraphQLEnumType).getValue("ALLOWED")!!
assertEquals(enumValue.getAppliedDirective("owner").getArgument("team")?.getValue<String>(), "books-team")
}

@Test
fun `should apply directives on the schema and its extensions`() {
val schema = SchemaParser.newParser()
Expand Down Expand Up @@ -458,6 +506,16 @@ class DirectiveTest {
val name: String?
)

private class PersonQueryResolver : GraphQLQueryResolver {
fun contactEmail(): String? = null
fun updatePersonEmail(email: String?, backupEmail: String?): String? = email
fun updatePerson(person: PersonInput?, state: AllowedState?): String? = null
}

private data class PersonInput(
val email: String?
)

private class QueryResolver : GraphQLQueryResolver {
fun books(): List<Book> {
return listOf(Book(42L, "Test Book"))
Expand Down Expand Up @@ -489,6 +547,26 @@ class DirectiveTest {
}
}

private class EmailDirective : SchemaDirectiveWiring {
val messages = mutableMapOf<String, Pair<String?, String?>>()

override fun onField(environment: SchemaDirectiveWiringEnvironment<GraphQLFieldDefinition>): GraphQLFieldDefinition {
recordMessage(environment)
return environment.element
}

override fun onArgument(environment: SchemaDirectiveWiringEnvironment<GraphQLArgument>): GraphQLArgument {
recordMessage(environment)
return environment.element
}

private fun recordMessage(environment: SchemaDirectiveWiringEnvironment<*>) {
val appliedMessage = environment.appliedDirective.getArgument("message")?.getValue<String>()
val legacyMessage = environment.directive.getArgument("message")?.let { GraphQLArgument.getArgumentValue<String>(it) }
messages[environment.element.name] = appliedMessage to legacyMessage
}
}

private class UppercaseDirective : SchemaDirectiveWiring {
override fun onObject(environment: SchemaDirectiveWiringEnvironment<GraphQLObjectType>): GraphQLObjectType {
val objectType = environment.element
Expand Down
Loading