mcarleio / konvert

This kotlin compiler plugin is using KSP API and generates kotlin code to map one class to another
https://mcarleio.github.io/konvert/
Apache License 2.0
93 stars 8 forks source link

Using interface as converter is failing in runtime #13

Closed jakoss closed 1 year ago

jakoss commented 1 year ago

I have that for a test:

// Core module
@Konverter
interface DateTimeConverters {
    fun asOffsetDateTime(source: LocalDateTime): OffsetDateTime = source.atOffset(localZoneOffset)

    companion object {
        val localZoneOffset: ZoneOffset by lazy { OffsetDateTime.now().offset }
    }
}

// weather module
@Konverter
interface WeatherMapper : DateTimeConverters {
    fun fromDto(weatherDto: CurrentWeatherDto): CurrentWeather
    fun toDto(weather: CurrentWeather): CurrentWeatherDto
}

Generated code looks like this:

public class WeatherMapperImpl : WeatherMapper {
  public override fun fromDto(weatherDto: CurrentWeatherDto): CurrentWeather = CurrentWeather(
    temperature = weatherDto.temperature,
    windspeed = weatherDto.windspeed,
    time = weatherDto.time.toLocalDateTime()
  )

  public override fun toDto(weather: CurrentWeather): CurrentWeatherDto = CurrentWeatherDto(
    temperature = weather.temperature,
    windspeed = weather.windspeed,
    time = io.mcarle.konvert.api.Konverter.get<pl.test.data.weather.impl.WeatherMapper>().asOffsetDateTime(source = weather.time)
  )
}

And when i try to call toDto i have exception:

java.util.NoSuchElementException: No element of the collection was transformed to a non-null value.
    at io.mcarle.konvert.api.Konverter$Companion.get(Konverter.kt:51)
    at pl.test.data.weather.impl.WeatherMapperImpl.toDto(WeatherMapperKonverter.kt:19)

Seems like instead of doing this.asOffsetDateTime the generated code tries to lookup the mapper which is not registered yet

mcarleio commented 1 year ago

At the moment it is still expected to call io.mcarle.konvert.api.Konverter.get<pl.test.data.weather.impl.WeatherMapper>().asOffsetDateTime(source = weather.time) instead of this.asOffsetDateTime(source = weather.time). That will change in future, but for now OK.

Could you test if adding your classloader manually helps:

fun main() {
   Konverter.addClassLoader(WeatherMapper::class.java.classLoader)
   val result = Konverter.get<WeatherMapper>().asOffsetDateTime(LocalDateTime.now())
   println(result)
}
jakoss commented 1 year ago

This works:

Konverter.addClassLoader(WeatherMapper::class.java.classLoader!!)
val result = Konverter.get<WeatherMapper>()
val test = result.fromDto(weather.currentWeather)
val converted = weatherMapper.toDto(test)
println(converted)

Maybe this has something to do with android system class loader?

mcarleio commented 1 year ago

@jakoss I looked into that again and changed it a bit to always use the class loader of the requested interface class. It should work now. If not, please reopen again!

jakoss commented 1 year ago

@mcarleio i still have the same error, but i think that just might be a simple error :D I think you are not using the classloader array you created: image

mcarleio commented 1 year ago

...my fault, I fixed it... Thanks for the feedback :+1: