Skip to content

Commit 3ebe171

Browse files
authored
[compiler] Add Service.issueSeverity() (#6731)
* [compiler] Fine grained issue severities Closes #6516 * Add Gradle test * Keep binary compatibility
1 parent 94ad27c commit 3ebe171

File tree

19 files changed

+260
-92
lines changed

19 files changed

+260
-92
lines changed

.idea/dictionaries/project.xml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/Issue.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ class IncompatibleDefinition(
6363
/**
6464
* Fields have different shapes and cannot be merged
6565
*
66+
* There is a discussion about whether this can be relaxed.
67+
* See https://github.com/apollographql/apollo-kotlin/issues/4320.
6668
*/
6769
class DifferentShape(override val message: String, override val sourceLocation: SourceLocation?) : GraphQLValidationIssue
6870

libraries/apollo-compiler/api/apollo-compiler.api

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -286,16 +286,15 @@ public final class com/apollographql/apollo/compiler/InputFile {
286286

287287
public final class com/apollographql/apollo/compiler/IrOptions {
288288
public static final field Companion Lcom/apollographql/apollo/compiler/IrOptions$Companion;
289-
public fun <init> (Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Set;Ljava/lang/String;)V
289+
public fun <init> (Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/Map;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Set;Ljava/lang/String;)V
290290
public final fun getAddTypename ()Ljava/lang/String;
291291
public final fun getAlwaysGenerateTypesMatching ()Ljava/util/Set;
292292
public final fun getCodegenModels ()Ljava/lang/String;
293293
public final fun getDecapitalizeFields ()Ljava/lang/Boolean;
294294
public final fun getFailOnWarnings ()Ljava/lang/Boolean;
295-
public final fun getFieldsOnDisjointTypesMustMerge ()Ljava/lang/Boolean;
296295
public final fun getFlattenModels ()Ljava/lang/Boolean;
297296
public final fun getGenerateOptionalOperationVariables ()Ljava/lang/Boolean;
298-
public final fun getWarnOnDeprecatedUsages ()Ljava/lang/Boolean;
297+
public final fun getIssueSeverities ()Ljava/util/Map;
299298
}
300299

301300
public final class com/apollographql/apollo/compiler/IrOptions$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
@@ -312,6 +311,15 @@ public final class com/apollographql/apollo/compiler/IrOptions$Companion {
312311
public final fun serializer ()Lkotlinx/serialization/KSerializer;
313312
}
314313

314+
public final class com/apollographql/apollo/compiler/IssueSeverity : java/lang/Enum {
315+
public static final field Error Lcom/apollographql/apollo/compiler/IssueSeverity;
316+
public static final field Ignore Lcom/apollographql/apollo/compiler/IssueSeverity;
317+
public static final field Warn Lcom/apollographql/apollo/compiler/IssueSeverity;
318+
public static fun getEntries ()Lkotlin/enums/EnumEntries;
319+
public static fun valueOf (Ljava/lang/String;)Lcom/apollographql/apollo/compiler/IssueSeverity;
320+
public static fun values ()[Lcom/apollographql/apollo/compiler/IssueSeverity;
321+
}
322+
315323
public abstract interface class com/apollographql/apollo/compiler/JavaCodegenOpt {
316324
public abstract fun getClassesForEnumsMatching ()Ljava/util/List;
317325
public abstract fun getGenerateModelBuilders ()Ljava/lang/Boolean;
@@ -391,8 +399,8 @@ public final class com/apollographql/apollo/compiler/OptionsKt {
391399
public static final field MODELS_RESPONSE_BASED Ljava/lang/String;
392400
public static final fun buildCodegenOptions (Lcom/apollographql/apollo/compiler/TargetLanguage;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/lang/Boolean;Lcom/apollographql/apollo/compiler/JavaNullable;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;)Lcom/apollographql/apollo/compiler/CodegenOptions;
393401
public static synthetic fun buildCodegenOptions$default (Lcom/apollographql/apollo/compiler/TargetLanguage;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/lang/Boolean;Lcom/apollographql/apollo/compiler/JavaNullable;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/apollographql/apollo/compiler/CodegenOptions;
394-
public static final fun buildIrOptions (Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Set;Ljava/lang/String;)Lcom/apollographql/apollo/compiler/IrOptions;
395-
public static synthetic fun buildIrOptions$default (Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Set;Ljava/lang/String;ILjava/lang/Object;)Lcom/apollographql/apollo/compiler/IrOptions;
402+
public static final fun buildIrOptions (Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Set;Ljava/lang/String;Ljava/util/Map;)Lcom/apollographql/apollo/compiler/IrOptions;
403+
public static synthetic fun buildIrOptions$default (Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Set;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lcom/apollographql/apollo/compiler/IrOptions;
396404
public static final fun validate (Lcom/apollographql/apollo/compiler/CodegenOptions;)V
397405
}
398406

libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/ApolloCompiler.kt

Lines changed: 26 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,16 @@
11
package com.apollographql.apollo.compiler
22

33
import com.apollographql.apollo.annotations.ApolloDeprecatedSince
4-
import com.apollographql.apollo.ast.DeprecatedUsage
5-
import com.apollographql.apollo.ast.DifferentShape
6-
import com.apollographql.apollo.ast.DirectiveRedefinition
74
import com.apollographql.apollo.ast.ForeignSchema
85
import com.apollographql.apollo.ast.GQLDefinition
96
import com.apollographql.apollo.ast.GQLDocument
107
import com.apollographql.apollo.ast.GQLFragmentDefinition
118
import com.apollographql.apollo.ast.GQLOperationDefinition
129
import com.apollographql.apollo.ast.GQLSchemaDefinition
1310
import com.apollographql.apollo.ast.GQLTypeDefinition
14-
import com.apollographql.apollo.ast.IncompatibleDefinition
1511
import com.apollographql.apollo.ast.Issue
1612
import com.apollographql.apollo.ast.ParserOptions
1713
import com.apollographql.apollo.ast.QueryDocumentMinifier
18-
import com.apollographql.apollo.ast.UnusedFragment
19-
import com.apollographql.apollo.ast.UnusedVariable
2014
import com.apollographql.apollo.ast.builtinForeignSchemas
2115
import com.apollographql.apollo.ast.checkEmpty
2216
import com.apollographql.apollo.ast.internal.SchemaValidationOptions
@@ -55,11 +49,13 @@ object ApolloCompiler {
5549
fun debug(message: String)
5650
fun info(message: String)
5751
fun warning(message: String)
52+
5853
@Deprecated("use warning instead", replaceWith = ReplaceWith("warning(message)"))
5954
@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0)
6055
fun warn(message: String) {
6156
warning(message)
6257
}
58+
6359
fun error(message: String)
6460
}
6561

@@ -106,7 +102,7 @@ object ApolloCompiler {
106102
}
107103
val mainSchemaDocument = mainSchemaDocuments.single()
108104

109-
// Sort the other schema document as type extensions are order sensitive, and we want this to be under the user control
105+
// Sort the other schema document as type extensions are order-sensitive, and we want this to be under the user control
110106
val otherSchemaDocumentSorted = otherSchemaDocuments.sortedBy { it.sourceLocation?.filePath?.substringAfterLast(File.pathSeparator) }
111107

112108
val schemaDefinitions = (listOf(mainSchemaDocument) + otherSchemaDocumentSorted).flatMap { it.definitions }
@@ -154,7 +150,8 @@ object ApolloCompiler {
154150
)
155151
)
156152

157-
val issueGroup = result.issues.group(warnOnDeprecatedUsages = true, fieldsOnDisjointTypesMustMerge = true)
153+
// TODO: allow the user to override the severities for schema validation
154+
val issueGroup = result.issues.group(defaultIssueSeverities)
158155

159156
issueGroup.errors.checkEmpty()
160157
issueGroup.warnings.forEach {
@@ -232,6 +229,7 @@ object ApolloCompiler {
232229
val name = it.name ?: ""
233230
operationNameToNormalizedPath[name] = normalizedFile.normalizedPath
234231
}
232+
235233
is GQLFragmentDefinition -> fragmentNameToNormalizedPath[it.name] = normalizedFile.normalizedPath
236234
else -> Unit
237235
}
@@ -251,9 +249,9 @@ object ApolloCompiler {
251249
}
252250

253251
var document = ApolloExecutableDocumentTransform(options.addTypename ?: defaultAddTypename, !hasCacheCompilerPlugin).transform(
254-
schema = schema,
255-
document = GQLDocument(userDefinitions, sourceLocation = null),
256-
upstreamFragmentDefinitions
252+
schema = schema,
253+
document = GQLDocument(userDefinitions, sourceLocation = null),
254+
upstreamFragmentDefinitions
257255
)
258256

259257
if (documentTransform != null) {
@@ -282,9 +280,16 @@ object ApolloCompiler {
282280

283281
val flattenModels = options.flattenModels ?: flattenModels(codegenModels)
284282
val decapitalizeFields = options.decapitalizeFields ?: defaultDecapitalizeFields
285-
val warnOnDeprecatedUsages = options.warnOnDeprecatedUsages ?: defaultWarnOnDeprecatedUsages
283+
val issueSeverities = defaultIssueSeverities.let {
284+
if (options.issueSeverities != null) {
285+
it.toMutableMap().apply {
286+
putAll(options.issueSeverities)
287+
}
288+
} else {
289+
it
290+
}
291+
}
286292
val failOnWarnings = options.failOnWarnings ?: defaultFailOnWarnings
287-
val fieldsOnDisjointTypesMustMerge = options.fieldsOnDisjointTypesMustMerge ?: defaultFieldsOnDisjointTypesMustMerge
288293
val generateOptionalOperationVariables = options.generateOptionalOperationVariables ?: defaultGenerateOptionalOperationVariables
289294
val alwaysGenerateTypesMatching = options.alwaysGenerateTypesMatching ?: defaultAlwaysGenerateTypesMatching
290295

@@ -293,10 +298,7 @@ object ApolloCompiler {
293298
allIssues.addAll(checkCapitalizedFields(userDefinitions, checkFragmentsOnly = flattenModels))
294299
}
295300

296-
val issueGroup = allIssues.group(
297-
warnOnDeprecatedUsages,
298-
fieldsOnDisjointTypesMustMerge,
299-
)
301+
val issueGroup = allIssues.group(issueSeverities)
300302

301303
issueGroup.errors.checkEmpty()
302304

@@ -315,7 +317,7 @@ object ApolloCompiler {
315317
val operations = mutableListOf<GQLOperationDefinition>()
316318
val fragments = mutableListOf<GQLFragmentDefinition>()
317319
document.definitions.forEach {
318-
when(it) {
320+
when (it) {
319321
is GQLOperationDefinition -> operations.add(it)
320322
is GQLFragmentDefinition -> fragments.add(it)
321323
else -> Unit
@@ -602,41 +604,27 @@ object ApolloCompiler {
602604
}
603605
}
604606

605-
private enum class Severity {
606-
None,
607-
Warning,
608-
Error
609-
}
610-
611607
internal class IssueGroup(
612608
val ignored: List<Issue>,
613609
val warnings: List<Issue>,
614610
val errors: List<Issue>,
615611
)
616612

617613
internal fun List<Issue>.group(
618-
warnOnDeprecatedUsages: Boolean,
619-
fieldsOnDisjointTypesMustMerge: Boolean,
614+
issueSeverities: Map<String, IssueSeverity>,
620615
): IssueGroup {
621616
val ignored = mutableListOf<Issue>()
622617
val warnings = mutableListOf<Issue>()
623618
val errors = mutableListOf<Issue>()
624619

625620
forEach {
626-
val severity = when (it) {
627-
is DeprecatedUsage -> if (warnOnDeprecatedUsages) Severity.Warning else Severity.None
628-
is DifferentShape -> if (fieldsOnDisjointTypesMustMerge) Severity.Error else Severity.Warning
629-
is UnusedVariable -> Severity.Warning
630-
is UnusedFragment -> Severity.Warning
631-
is IncompatibleDefinition -> Severity.Warning // This should probably be an error
632-
is DirectiveRedefinition -> Severity.Warning
633-
else -> Severity.Error
634-
}
621+
val name = it.javaClass.simpleName
622+
val severity = issueSeverities.get(name) ?: IssueSeverity.Error
635623

636624
when (severity) {
637-
Severity.None -> ignored.add(it)
638-
Severity.Warning -> warnings.add(it)
639-
Severity.Error -> errors.add(it)
625+
IssueSeverity.Ignore -> ignored.add(it)
626+
IssueSeverity.Warn -> warnings.add(it)
627+
IssueSeverity.Error -> errors.add(it)
640628
}
641629
}
642630

libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/Options.kt

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ package com.apollographql.apollo.compiler
22

33
import com.apollographql.apollo.annotations.ApolloDeprecatedSince
44
import com.apollographql.apollo.annotations.ApolloExperimental
5+
import com.apollographql.apollo.ast.DeprecatedUsage
6+
import com.apollographql.apollo.ast.DirectiveRedefinition
7+
import com.apollographql.apollo.ast.IncompatibleDefinition
8+
import com.apollographql.apollo.ast.UnusedFragment
9+
import com.apollographql.apollo.ast.UnusedVariable
510
import com.apollographql.apollo.compiler.internal.sha256
611
import com.apollographql.apollo.compiler.operationoutput.OperationId
712
import com.apollographql.apollo.compiler.operationoutput.OperationOutput
@@ -156,20 +161,14 @@ class CodegenSchemaOptions(
156161
constructor(): this(emptyMap(), emptyMap(), false)
157162
}
158163

164+
enum class IssueSeverity {
165+
Ignore,
166+
Warn,
167+
Error
168+
}
169+
159170
@Serializable
160171
class IrOptions(
161-
/**
162-
* Whether fields with different shape are disallowed to be merged in disjoint types.
163-
*
164-
* Note: setting this to `false` relaxes the standard GraphQL [FieldsInSetCanMerge](https://spec.graphql.org/draft/#FieldsInSetCanMerge()) validation which may still be
165-
* run on the backend.
166-
*
167-
* See also [issue 4320](https://github.com/apollographql/apollo-kotlin/issues/4320)
168-
*
169-
* Default: true.
170-
*/
171-
val fieldsOnDisjointTypesMustMerge: Boolean?,
172-
173172
/**
174173
* Whether to decapitalize field names in the generated models (for instance `FooBar` -> `fooBar`).
175174
*
@@ -179,7 +178,7 @@ class IrOptions(
179178

180179
val flattenModels: Boolean?,
181180

182-
val warnOnDeprecatedUsages: Boolean?,
181+
val issueSeverities: Map<String, IssueSeverity>?,
183182
val failOnWarnings: Boolean?,
184183
val addTypename: String?,
185184

@@ -203,25 +202,23 @@ class IrOptions(
203202
)
204203

205204
fun buildIrOptions(
206-
fieldsOnDisjointTypesMustMerge: Boolean? = null,
207205
decapitalizeFields: Boolean? = null,
208206
flattenModels: Boolean? = null,
209-
warnOnDeprecatedUsages: Boolean? = null,
210207
failOnWarnings: Boolean? = null,
211208
addTypename: String? = null,
212209
generateOptionalOperationVariables: Boolean? = null,
213210
alwaysGenerateTypesMatching: Set<String>? = null,
214211
codegenModels: String? = null,
212+
issueSeverity: Map<String, IssueSeverity>? = null,
215213
): IrOptions = IrOptions(
216-
fieldsOnDisjointTypesMustMerge = fieldsOnDisjointTypesMustMerge,
217214
decapitalizeFields = decapitalizeFields,
218215
flattenModels = flattenModels,
219-
warnOnDeprecatedUsages = warnOnDeprecatedUsages,
220216
failOnWarnings = failOnWarnings,
221217
addTypename = addTypename,
222218
generateOptionalOperationVariables = generateOptionalOperationVariables,
223219
alwaysGenerateTypesMatching = alwaysGenerateTypesMatching,
224220
codegenModels = codegenModels,
221+
issueSeverities = issueSeverity
225222
)
226223

227224
interface CommonCodegenOpt {
@@ -685,7 +682,13 @@ internal val defaultOperationIdsGenerator = OperationIdsGenerator { operationDes
685682

686683
internal val defaultLogger = NoOpLogger
687684
internal const val defaultUseSemanticNaming = true
688-
internal const val defaultWarnOnDeprecatedUsages = true
685+
internal val defaultIssueSeverities = mapOf(
686+
DeprecatedUsage::class.simpleName!! to IssueSeverity.Warn,
687+
UnusedVariable::class.simpleName!! to IssueSeverity.Warn,
688+
UnusedFragment::class.simpleName!! to IssueSeverity.Warn,
689+
DirectiveRedefinition::class.simpleName!! to IssueSeverity.Warn,
690+
IncompatibleDefinition::class.simpleName!! to IssueSeverity.Warn, // This should probably be an error by default
691+
)
689692
internal const val defaultFailOnWarnings = false
690693
internal const val defaultGenerateAsInternal = false
691694
internal const val defaultGenerateFilterNotNull = false

libraries/apollo-compiler/src/test/kotlin/com/apollographql/apollo/compiler/TestUtils.kt

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ internal object TestUtils {
4848
}
4949

5050
/**
51-
* This allows to run a specific test from the command line by using something like:
51+
* This allows running a specific test from the command line by using something like:
5252
*
5353
* ./gradlew :apollo-compiler:test -testFilter="fragments_with_type_condition" --tests '*Codegen*'
5454
*/
@@ -71,10 +71,6 @@ internal object TestUtils {
7171
.map { arrayOf(it.nameWithoutExtension, it) }
7272
}
7373

74-
private fun File.replaceExtension(newExtension: String): File {
75-
return File(parentFile, "$nameWithoutExtension.$newExtension")
76-
}
77-
7874
fun findSchema(dir: File): Schema? {
7975
return listOf("graphqls", "sdl", "json").map { File(dir, "schema.$it") }
8076
.firstOrNull { it.exists() }
@@ -86,7 +82,7 @@ internal object TestUtils {
8682
/**
8783
* run the block and checks the result against the .expected file
8884
*
89-
* @param block: the callback to produce the result. [checkExpected] will try to find a schema
85+
* @param block the callback to produce the result. [checkExpected] will try to find a schema
9086
* for [graphQLFile] by either looking for a schema with the same name or testing the first
9187
* schema.[json|sdl|graphqls] in the hierarchy
9288
*/
@@ -112,7 +108,7 @@ internal object TestUtils {
112108
val expectedFile = File(graphQLFile.parent, graphQLFile.nameWithoutExtension + ".expected")
113109
val expected = try {
114110
expectedFile.readText()
115-
} catch (e: Exception) {
111+
} catch (_: Exception) {
116112
null
117113
}
118114

@@ -131,7 +127,7 @@ internal object TestUtils {
131127
internal fun String.buffer() = Buffer().writeUtf8(this)
132128

133129
internal fun <V : Any> GQLResult<V>.apolloGetOrThrow(): V {
134-
val groups = issues.group(false, true)
130+
val groups = issues.group(defaultIssueSeverities)
135131

136132
groups.errors.firstOrNull()?.let {
137133
throw SourceAwareException(it.message, it.sourceLocation)

0 commit comments

Comments
 (0)