Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 Bug Report: Kotlin server SDK does not properly process nullable values when mapping #667

Closed
2 tasks done
Narmo opened this issue Jun 14, 2023 · 2 comments
Closed
2 tasks done
Assignees
Labels
bug Something isn't working

Comments

@Narmo
Copy link

Narmo commented Jun 14, 2023

👟 Reproduction steps

When I try to fetch teams list using Teams object, I get NPE.

Demo code:

val client = Client().setEndpoint(Application.appWriteEndpoint).setProject(Application.appWriteProjectId).setKey(Application.appWriteApiKey)
val teamsClient = Teams(client)

val teams = teamsClient.list().teams // this line causes NPE, see below

teams.forEach {
    println(it.name)
}

👍 Expected behavior

Teams list should load.

👎 Actual Behavior

The call crashes with following stack trace:

Exception in thread "OkHttp Dispatcher" java.lang.NullPointerException: null cannot be cast to non-null type kotlin.collections.Map<kotlin.String, kotlin.Any>
	at io.appwrite.models.Team$Companion.from(Team.kt:83)
	at io.appwrite.models.TeamList$Companion.from(TeamList.kt:43)
	at io.appwrite.services.Teams$list$converter$1.invoke(Teams.kt:43)
	at io.appwrite.services.Teams$list$converter$1.invoke(Teams.kt:42)
	at io.appwrite.Client$awaitResponse$2$1.onResponse(Client.kt:507)
	at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:833)

I've investigated the crash and found the point. The error occurs in mapping the Team object's preferences in Team.kt file:

@Suppress("UNCHECKED_CAST")
fun <T> from(
    map: Map<String, Any>,
    nestedType: Class<T>
) = Team<T>(
    id = map["\$id"] as String,
    createdAt = map["\$createdAt"] as String,
    updatedAt = map["\$updatedAt"] as String,
    name = map["name"] as String,
    total = (map["total"] as Number).toLong(),
    prefs = Preferences.from(map = map["prefs"] as Map<String, Any>, nestedType),
)

In last line, where prefs is assigned, actual value of maps["prefs"] is null, while cast expects it to be Map<String, Any>. So the prefs field in data class Team should be nullable, and I suggest that the cast should be rewritten, something like that:

prefs = (map["prefs"] as? Map<String, Any>)?.run { prefs = Preferences.from(map = this, nestedType),

I've checked the sdk-generator project and found that all models, when processing maps or lists of maps, always assume that the input value is not nullable, while it actually can be null (and so this is true for corresponding fields in data classes, which have to be nullable too).

Here is example from templates/kotlin/src/main/kotlin/io/appwrite/models/Model.kt.twig, line 64, where aforementioned code for Kotlin Server SDK is being generated:

{{ property.name | escapeKeyword | removeDollarSign }} = {% if property.sub_schema %}{% if property.type == 'array' %}(map["{{ property.name | escapeDollarSign }}"] as List<Map<String, Any>>).map { {{ property.sub_schema | caseUcfirst }}.from(map = it{% if definition.name | hasGenericType(spec) %}, nestedType{% endif %}) }{% else %}{{ property.sub_schema | caseUcfirst }}.from(map = map["{{property.name | escapeDollarSign }}"] as Map<String, Any>{% if definition.name | hasGenericType(spec) %}, nestedType{% endif %}){% endif %}{% else %}{% if property.type == "integer" or property.type == "number" %}({% endif %}map["{{ property.name | escapeDollarSign }}"]{% if property.type == "integer" or property.type == "number" %} as{% if not property.required %}?{% endif %} Number){% endif %}{% if property.type == "integer" %}{% if not property.required %}?{% endif %}.toLong(){% elseif property.type == "number" %}{% if not property.required %}?{% endif %}.toDouble(){% else %} as{% if not property.required %}?{% endif %} {{ property | propertyType(spec) | raw }}{% endif %}{% endif %},

Unfortunately due to my lack of knowledge of Twig or PHP stops me from creating proper pull-request for this feature, so I hope that someone will be able to fix this in future releases.

I've created similar issue in sdk-for-kotlin repo before discovering this repo, so please remove that issue if it is not required anymore: appwrite/sdk-for-kotlin#30.

🎲 Appwrite version

Different version (specify in environment)

💻 Operating system

MacOS

🧱 Your Environment

I use io.appwrite:sdk-for-kotlin:2.0.0 which automatically fetches all required dependencies.

👀 Have you spent some time to check if this issue has been raised before?

  • I checked and didn't find similar issue

🏢 Have you read the Code of Conduct?

@Narmo Narmo added the bug Something isn't working label Jun 14, 2023
@christyjacob4
Copy link
Member

@Narmo Thanks a lot for raising this issue. We will get someone to work on this :)

@abnegate
Copy link
Contributor

abnegate commented May 3, 2024

This was fixed server-side

@abnegate abnegate closed this as completed May 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants