Don't crash on missing data - Feature request
TSurkis opened this issue ยท 10 comments
About this issue
I've used this library extensively and on rare occasions the value of name for a library won't exist. It is weird then, that the entire parsing mechanism crashes on a value that doesn't seem that important.
We can provide a new flag that would represent a recoverability option for those who do not wish for the entire parsing method to crash. This flag will default values for non null fields that ended up being null:
- name - can be replaced by
"uniqueId"
which represents the package name. - developers - can be replaced by an
emptyList()
if none exist.
Checklist
- Searched for similar issues
- Checked out the sample application
- Read the README
- Checked out the CHANGELOG
- Read the MIGRATION GUIDE
Thank you very much for the report.
I agree, there should not be a crash in those cases, and that it's a good idea to make it more secure.
Do you have an example for a dependency which misses the title, I'd like to check the plugin itself, given the data generation should already handle some parts of this.
@mikepenz No example as of now sadly. I am opening a PR shortly with an offered solution.
@mikepenz I have no permissions, even pushing to a side branch
Here's my suggested solution:
Libs.kt
data class Libs constructor(
val libraries: List<Library>,
val licenses: Set<License>,
) {
/**
* Builder used to automatically parse and interpret the generated library data from the plugin.
*/
class Builder {
private var _stringData: String? = null
private var recoverable: Boolean = false
/**
* Provide the generated library data as [String]
*/
fun withJson(stringData: String): Builder {
_stringData = stringData
return this
}
/**
* Don't crash on missing library data. Instead fill in:
* [Library.name] = use [Library.uniqueId]
* [Library.developers] = use [emptyList]
*/
fun recoverableMissingData(recoverable: Boolean): Builder {
this.recoverable = recoverable
return this
}
/**
* Build the [Libs] instance with the applied configuration.
*/
fun build(): Libs {
val data = _stringData
val (libraries, licenses) = if (data != null) {
parseData(data, recoverable)
} else {
throw IllegalStateException(
"""
Please provide the required library data via the available APIs.
Depending on the platform this can be done for example via `LibsBuilder().withJson()`.
For Android there exists an `LibsBuilder.withContext()`, automatically loading the `aboutlibraries.json` file from the `raw` resources folder.
When using compose or other parent modules, please check their corresponding APIs.
""".trimIndent()
)
}
return Libs(libraries.sortedBy { it.name.lowercase() }, licenses.toMutableSet())
}
}
}
AndroidParser.kt
actual fun parseData(json: String, recoverable: Boolean): Result {
try {
val metaData = JSONObject(json)
val licenses = metaData.getJSONObject("licenses").forEachObject { key ->
License(
getString("name"),
optString("url"),
optString("year"),
optString("spdxId"),
optString("content"),
key
)
}
val mappedLicenses = licenses.associateBy { it.hash }
val libraries = metaData.getJSONArray("libraries").forEachObject {
val libLicenses = optJSONArray("licenses").forEachString { mappedLicenses[this] }.mapNotNull { it }.toHashSet()
val developers = optJSONArray("developers")?.forEachObject {
Developer(optString("name"), optString("organisationUrl"))
} ?: emptyList()
val organization = optJSONObject("organization")?.let {
Organization(it.getString("name"), it.optString("url"))
}
val scm = optJSONObject("scm")?.let {
Scm(it.optString("connection"), it.optString("developerConnection"), it.optString("url"))
}
val funding = optJSONArray("funding").forEachObject {
Funding(getString("platform"), getString("url"))
}.toSet()
val id = getString("uniqueId")
Library(
id,
optString("artifactVersion"),
if (recoverable) optString("name", id) else getString("name"),
optString("description"),
optString("website"),
developers,
organization,
scm,
libLicenses,
funding,
optString("tag")
)
}
return Result(libraries, licenses)
} catch (t: Throwable) {
Log.e("AboutLibraries", "Failed to parse the meta data *.json file: $t")
}
return Result(emptyList(), emptyList())
}
MultiplatformParser.kt
actual fun parseData(json: String, recoverable: Boolean): Result {
try {
val metaData = Json.parseToJsonElement(json).jsonObject
val licenses = metaData.getJSONObject("licenses").forEachObject { key ->
License(
getString("name"),
optString("url"),
optString("year"),
optString("spdxId"),
optString("content"),
key
)
}
val mappedLicenses = licenses.associateBy { it.hash }
val libraries = metaData.getJSONArray("libraries").forEachObject {
val libLicenses = optJSONArray("licenses").forEachString { mappedLicenses[this] }.mapNotNull { it }.toHashSet()
val developers = optJSONArray("developers")?.forEachObject {
Developer(optString("name"), optString("organisationUrl"))
} ?: emptyList()
val organization = optJSONObject("organization")?.let {
Organization(it.getString("name"), it.optString("url"))
}
val scm = optJSONObject("scm")?.let {
Scm(it.optString("connection"), it.optString("developerConnection"), it.optString("url"))
}
val funding = optJSONArray("funding").forEachObject {
Funding(getString("platform"), getString("url"))
}.toSet()
val id = getString("uniqueId")
Library(
id,
optString("artifactVersion"),
if (recoverable) optString("name") ?: id else getString("name"),
optString("description"),
optString("website"),
developers,
organization,
scm,
libLicenses,
funding,
optString("tag")
)
}
return Result(libraries, licenses)
} catch (t: Throwable) {
println("Failed to parse the meta data *.json file: $t")
}
return Result(emptyList(), emptyList())
}
The easiest way to reproduce is to put these lines after the first line in the parseData
method
val firstItem: JSONObject = metaData.getJSONArray("libraries")[0] as JSONObject
firstItem.remove("name")
firstItem.remove("developers")
@TSurkis you'll have to fork the project, create a branch on your fork make the modifications. And then you can open a PR with it :)
https://docs.github.com/en/get-started/quickstart/contributing-to-projects
Thank you very much
@mikepenz Thanks! Is there a timeline or an estimate to when this will be released?
Available as part of 10.7.0 (currently being synced to maven central)