Skip to content

Commit

Permalink
chore(#83): improve exceptions with exception chaining
Browse files Browse the repository at this point in the history
This is a first easy step by catching exceptions during code generation and throw a KonvertException which contain basic information which might be missing in the original exception
  • Loading branch information
mcarleio committed Jul 2, 2024
1 parent 5d13e5d commit 6e7fd81
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import io.mcarle.konvert.converter.api.config.enforceNotNull
import io.mcarle.konvert.converter.api.isNullable
import io.mcarle.konvert.processor.AnnotatedConverter
import io.mcarle.konvert.processor.DefaultSourceDataExtractionStrategy
import io.mcarle.konvert.processor.exceptions.KonvertException
import io.mcarle.konvert.processor.exceptions.NotNullOperatorNotEnabledException
import io.mcarle.konvert.processor.exceptions.PropertyMappingNotExistingException

Expand All @@ -38,55 +39,59 @@ class CodeGenerator constructor(
mappingCodeParentDeclaration: KSDeclaration,
additionalSourceParameters: List<KSValueParameter>
): CodeBlock {
if (paramName != null) {
val existingTypeConverter = TypeConverterRegistry
.firstOrNull {
it.matches(source, target) && it !is AnnotatedConverter
try {
if (paramName != null) {
val existingTypeConverter = TypeConverterRegistry
.firstOrNull {
it.matches(source, target) && it !is AnnotatedConverter
}

if (existingTypeConverter != null) {
return CodeBlock.of(
"return·%L",
existingTypeConverter.convert(paramName, source, target)
)
}

if (existingTypeConverter != null) {
return CodeBlock.of(
"return·%L",
existingTypeConverter.convert(paramName, source, target)
)
}
}

val sourceProperties = PropertyMappingResolver(
logger,
DefaultSourceDataExtractionStrategy(mappingCodeParentDeclaration, resolver.builtIns.unitType)
)
.determinePropertyMappings(
paramName,
mappings,
source,
additionalSourceParameters
val sourceProperties = PropertyMappingResolver(
logger,
DefaultSourceDataExtractionStrategy(mappingCodeParentDeclaration, resolver.builtIns.unitType)
)
.determinePropertyMappings(
paramName,
mappings,
source,
additionalSourceParameters
)

val targetClassDeclaration = target.classDeclaration()!!
val targetClassDeclaration = target.classDeclaration()!!

val constructor = ConstructorResolver(logger)
.determineConstructor(mappingCodeParentDeclaration, targetClassDeclaration, sourceProperties, constructorTypes)
val constructor = ConstructorResolver(logger)
.determineConstructor(mappingCodeParentDeclaration, targetClassDeclaration, sourceProperties, constructorTypes)

val targetElements = determineTargetElements(sourceProperties, constructor, targetClassDeclaration)
val targetElements = determineTargetElements(sourceProperties, constructor, targetClassDeclaration)

verifyPropertiesAndMandatoryParametersExist(sourceProperties, targetElements)
verifyPropertiesAndMandatoryParametersExist(sourceProperties, targetElements)

if (source.isNullable() && !target.isNullable() && !Configuration.enforceNotNull) {
throw NotNullOperatorNotEnabledException(paramName, source, target)
}
if (source.isNullable() && !target.isNullable() && !Configuration.enforceNotNull) {
throw NotNullOperatorNotEnabledException(paramName, source, target)
}

val targetPropertiesWithoutParameters = extractDistinctProperties(targetElements)

val targetPropertiesWithoutParameters = extractDistinctProperties(targetElements)

return MappingCodeGenerator().generateMappingCode(
source,
target,
sourceProperties.sortedByDescending { it.isBasedOnAnnotation },
constructor,
paramName,
targetClassImportName,
targetPropertiesWithoutParameters
)
return MappingCodeGenerator().generateMappingCode(
source,
target,
sourceProperties.sortedByDescending { it.isBasedOnAnnotation },
constructor,
paramName,
targetClassImportName,
targetPropertiesWithoutParameters
)
} catch (e: Exception) {
throw KonvertException(source, target, e)
}
}

private fun extractDistinctProperties(targetElements: List<TargetElement>) = targetElements
Expand Down Expand Up @@ -160,10 +165,10 @@ class CodeGenerator constructor(
constructor(annotated: KSAnnotated) : this(annotated as? KSPropertyDeclaration, annotated as? KSValueParameter)

override fun toString(): String {
return if (property != null) {
"propertyDeclaration=$property"
} else {
"valueParameter=$parameter"
return when {
property != null -> property.simpleName.asString()
parameter != null -> parameter.name?.asString() ?: parameter.toString()
else -> error("No property or parameter available")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSFunctionDeclaration

class AmbiguousConstructorException(classDeclaration: KSClassDeclaration, constructors: List<KSFunctionDeclaration>) :
RuntimeException("Ambiguous constructors for $classDeclaration: ${constructors.map { c -> c.parameters.map { p -> p.type } }}")
RuntimeException("Ambiguous constructors for $classDeclaration: ${constructors.map { c -> c.parameters.map { p -> p.type } }.joinToString(", ")}")
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.mcarle.konvert.processor.exceptions

import com.google.devtools.ksp.symbol.KSType

class KonvertException(
source: KSType,
target: KSType,
cause: Exception
) : RuntimeException(
"Error while processing $source -> $target",
cause
)
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ class NotNullOperatorNotEnabledException : RuntimeException {
)

constructor(sourceName: String?, sourceType: KSType, targetName: String?, targetType: KSType) : this(
source = if (sourceName.isNullOrEmpty()) "$sourceType" else "$sourceName.$sourceType",
target = if (targetName.isNullOrEmpty()) "$targetType" else "$targetName.$targetType"
source = if (sourceName.isNullOrEmpty()) "$sourceType" else "$sourceName:$sourceType",
target = if (targetName.isNullOrEmpty()) "$targetType" else "$targetName:$targetType"
)

private constructor(source: String, target: String) : super(
"""
Mapping from $source to $target without !! operator not possible.
Consider allowing such mappings with the option ${ENFORCE_NOT_NULL_OPTION.key}
Consider allowing such mappings with the option `${ENFORCE_NOT_NULL_OPTION.key}` if you are sure that the source value is never null
- otherwise this may lead to runtime exceptions!
You can configure this globally or for a single converter:
@KonvertTo(TargetClass::class, options = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import io.mcarle.konvert.processor.codegen.CodeGenerator
import io.mcarle.konvert.processor.codegen.PropertyMappingInfo

class PropertyMappingNotExistingException(target: String, propertyMappings: List<PropertyMappingInfo>) : RuntimeException(
"No property for $target existing in $propertyMappings"
"No property for `$target` existing. Available mappings are: ${propertyMappings.map { it.targetName }}"
) {
constructor(ksValueParameter: KSValueParameter, propertyMappings: List<PropertyMappingInfo>) : this(
ksValueParameter.toString(),
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import io.mcarle.konvert.converter.api.config.ENABLE_CONVERTERS_OPTION
import io.mcarle.konvert.processor.exceptions.AmbiguousConstructorException
import io.mcarle.konvert.processor.exceptions.NoMatchingConstructorException
import io.mcarle.konvert.processor.exceptions.NotNullOperatorNotEnabledException
import io.mcarle.konvert.processor.exceptions.PropertyMappingNotExistingException
import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
import org.junit.jupiter.api.Test
import kotlin.test.assertContains
Expand Down Expand Up @@ -727,6 +728,31 @@ class TargetClass(val property: String)
assertContains(compilationResult.messages, NotNullOperatorNotEnabledException::class.qualifiedName!!)
}

@Test
fun throwPropertyMappingNotExistingException() {
val (_, compilationResult) = compileWith(
enabledConverters = listOf(SameTypeConverter()),
expectResultCode = KotlinCompilation.ExitCode.COMPILATION_ERROR,
code = SourceFile.kotlin(
name = "TestCode.kt",
contents =
"""
import io.mcarle.konvert.api.Konverter
@Konverter
interface Mapper {
fun toTarget(source: SourceClass): TargetClass
}
class SourceClass(val someDifferentPropertyName: String)
class TargetClass(val property: String)
""".trimIndent()
)
)
assertEquals(expected = KotlinCompilation.ExitCode.COMPILATION_ERROR, actual = compilationResult.exitCode)
assertContains(compilationResult.messages, PropertyMappingNotExistingException::class.qualifiedName!!)
}

@Test
fun workWithValueClasses() {
val (compilation) = compileWith(
Expand Down

0 comments on commit 6e7fd81

Please sign in to comment.