Python: introduce getOr("message"){} API to add errors on an appropriate level.

See `getOr` extension doc.

GitOrigin-RevId: 859af221e99f03b99ee2f82e829e83e0f00a9e6d
This commit is contained in:
Ilya.Kazakevich
2025-06-07 17:33:46 +02:00
committed by intellij-monorepo-bot
parent cea3e7d9c0
commit 105554c91d
17 changed files with 84 additions and 59 deletions

View File

@@ -25,7 +25,7 @@ class ExecError(
/**
* optional message to be displayed to the user: Why did we run this process. I.e "running pip to install package".
*/
val additionalMessageToUser: @NlsContexts.DialogTitle String? = null,
additionalMessageToUser: @NlsContexts.DialogTitle String? = null,
) : PyError(getExecErrorMessage(exe.toString(), args, additionalMessageToUser, errorReason)) {
val asCommand: String get() = (arrayOf(exe.toString()) + args).joinToString(" ")
}

View File

@@ -2,6 +2,8 @@
package com.jetbrains.python.errorProcessing
import com.intellij.openapi.util.NlsSafe
import org.jetbrains.annotations.Nls
import java.util.concurrent.CopyOnWriteArrayList
/**
* Error that is interested to user.
@@ -9,9 +11,21 @@ import com.intellij.openapi.util.NlsSafe
* Those are *not* NPEs nor OOBs nor various assertions.
* Do *not* use `catch(Exception)` or `runCatching` with this class.
*
* Use [message] to provide user-readable error description i.e "could not connect to the Internet".
* Upper levels will add additional information there.
*
* Most probably you will send this error to [ErrorSink].
*/
sealed class PyError(val message: @NlsSafe String) {
sealed class PyError(message: @NlsSafe String) {
private val _messages = CopyOnWriteArrayList<@Nls String>(listOf(message))
val message: @Nls String get() = _messages.reversed().joinToString(": ")
/**
* To be used by [getOr], see it for more info
*/
fun addMessage(message: @Nls String) {
_messages.add(message)
}
override fun toString(): String = message
}

View File

@@ -2,6 +2,7 @@
package com.jetbrains.python.errorProcessing
import com.jetbrains.python.packaging.PyExecutionException
import org.jetbrains.annotations.Nls
/**
* This class is expected to be used as a return value of most PyCharm APIs.
@@ -14,12 +15,12 @@ typealias PyResult<T> = com.jetbrains.python.Result<T, PyError>
*/
typealias PyExecResult<T> = com.jetbrains.python.Result<T, ExecError>
inline fun <reified T : PyError> failure(pyError: T): com.jetbrains.python.Result.Failure<T> = com.jetbrains.python.Result.Companion.failure(pyError)
inline fun <reified T : PyError> failure(pyError: T): com.jetbrains.python.Result.Failure<T> = com.jetbrains.python.Result.failure(pyError)
@Deprecated("Migrate to native python result")
fun <T> Result<T>.asPythonResult(): com.jetbrains.python.Result<T, PyError> =
com.jetbrains.python.Result.Companion.success(getOrElse {
com.jetbrains.python.Result.success(getOrElse {
return if (it is PyExecutionException) {
failure(it.pyError)
}
@@ -37,3 +38,26 @@ fun <S> PyResult<S>.asKotlinResult(): Result<S> = when (this) {
is com.jetbrains.python.Result.Failure -> Result.failure(PyExecutionException(error))
}
/***
* When returning error one level up, you must add description on what has happened, i.e:
* ```kotlin
* fun createUser(): PyResult<User> {
* val data = readFromJson().getOr("failed to read from json file $file") { return it }
* return User.parse(data)
* }
*
* fun launchRocket():PyResult<Rocket> {
* val pilot = createUser().getOr("Could not get pilot"){return it}
* }
* ```
* This will provide readable chain to user: Can't get pilot: Can't create user: Can't read file.
*/
inline fun <SUCC, ERR : PyError> com.jetbrains.python.Result<SUCC, ERR>.getOr(actionFailed: @Nls String, onFailure: (err: com.jetbrains.python.Result.Failure<ERR>) -> Nothing): SUCC {
when (this) {
is com.jetbrains.python.Result.Failure -> {
error.addMessage(actionFailed)
onFailure(this)
}
is com.jetbrains.python.Result.Success -> return result
}
}