Custom Errors #
Pragma Engine supports two different error types: application errors and service errors.
- Service errors indicate a critical error that requires attention.
- Application errors handle expected errors.
Pragma Engine utilizes these errors throughout the engine, and custom ones can be used in plugins, player data modules, and custom services.
Service errors should always be used to indicate a critical error requiring further attention and not application errors.
Continue reading to learn more about what these errors are and how to author custom ones.
Service errors #
Service errors help indicate that an unexpected outcome has occurred. They alert the client SDK, game servers, and backend instances to help fix the error.
Custom service errors #
To create a new custom error, add a new enum type to the ExtError
option in the 5-ext/ext-protos/src/main/proto/extensions/errorsExt.proto
file.
enum ExtError {
option (unreal_enum_name) = "ExtError";
UNKNOWN_EXT_ERROR = 0;
MY_CUSTOM_ERROR = 1;
}
Remember to run make ext-protos
to register these changes before attempting to reference the new custom error.
To use the custom error, throw an ExtException
with the new ExtError
enum value as the error parameter.
throw ExtException(ExtError.MY_CUSTOM_ERROR, "something went wrong")
This is translated into a ServiceError
returned to the client SDK. It can then be handled similarly to the standard provided PragmaErrors
.
Application errors #
Application errors indicate that an expected or planned error type has occurred. These errors contain data that is sent back to the client and game servers to indicate why successful outcomes of a request couldn’t be completed.
For example, if a custom Party plugin has logic for incorporating players’ rank into matchmaking, a custom application error can return custom data back to the game client to show which pairs of players have incompatible ranks in the party.
Custom application errors #
Create and use custom application errors by following the steps below:
- Define the error’s protobufs.
- Utilize the errors with the application error exception class.
- Author logic in the PragmaSDK for checking the return types for an application error.
Continue reading the sections below for more implementation details.
Application error protobufs #
Application errors are defined using protobufs and can be authored anywhere under 5-ext/ext-protos/
. Once defined, they are then used with the ApplicationErrorException
class.
Every application error protobuf requires the Pragma defined option (force_include_type) = true
field in order to ensure the error is properly generated. Run make ext-protos
to generate the application error for use.
Below is an example proto message of an application error:
message DemoPluginApplicationError {
option (force_include_type) = true;
string message = 1;
}
Application error exception #
The ApplicationErrorException
class is used to throw application errors. This class will contain a protbuf and is a RuntimeException()
.
class ApplicationErrorException(
val errorMessage: GeneratedMessageV3
) : RuntimeException()
You can throw an ApplicationErrorException
in any layer of Pragma Engine: plugins, custom services, etc. Below are functions available to help facilitate throwing application errors:
Helper function | Description |
---|---|
applicationError(builder: () -> GeneratedMessageV3): | Throws an application error. This function has one parameter–a block that returns an application error proto. |
applicationRequire(value: Boolean, lazyError: () | Throws an application error if the condition value is false. This is similar to Kotlin’s require function but throws a ApplicationErrorException instead. |
applicationRequireNotNull(value: T?, lazyError: () -> GeneratedMessageV3): | Throws an application error if the value is null. This is similar to Kotlin’s requireNotNull function but throws a ApplicationErrorException instead. |
The following example showcases using an application error in AccountPlugin.updateDisplayName()
.
override suspend fun updateDisplayName(requestedDisplayName: String, pragmaAccount: PragmaAccount) {
applicationRequire(requestedDisplayName != "banneddisplayname") {
DemoPluginApplicationError.newBuilder().setMessage("Inappropriate display name").build()
}
/// Logic for updating a display name …
}
Application error SDK #
Application errors are exposed as a PragmaResult
failure. Once exposed, the types for an application error can be checked before reading in Unity and Unreal. The caller always needs to know the types of application errors that can be returned on the endpoint. Note that endpoints could have both Pragma defined and your own custom application errors.
Service to service calls #
Application errors cannot be exposed through theService.requestRpc
API orPragmaNode.requestRpc
API. If an application error is thrown from a call using these APIs, the call will returnPragmaResult.Failure
withServiceError: PragmaError.Serialization_Error
.
If you’re making a service to service call and need to be aware of any application errors that occur, use the APIs listed below.
API Call | Description |
---|---|
Service.requestRpcV2: PragmaResult<Response, PragmaFailure> | Service request routed through a Service instance.Common use cases: plugins and custom services. |
Service.requestRpcV2AsPlayer: PragmaResult PragmaResult<Response, PragmaFailure | Service request routed through a Service instance. Only use this request if you have access to the player’s PlayerSession .Common use case: custom services. |
Service.requestRpcV2AsOperator: PragmaResult PragmaResult<Response, PragmaFailure> | Service request routed through a Service instance. Only use this request if you have access to an operator’s OperatorSession .Common use case: custom services. |
PragmaNode.requestRpcV2: PragmaResult<TypeInfoAndSerializedMessage, ServiceError> | Lower level service request routed through PragmaNode . The TypeInfoAndSerializedMessage will contain either the Response or an Application Error Proto |
PragmaResult
uses the PragmaFailure
class to expose errors. PragmaFailure
contains either a ServiceError
or an application error proto in a TypeInfoAndSerializedMessage
.
data class PragmaFailure(
val serviceError: ServiceError? = null,
val applicationError: TypeInfoAndSerializedMessage? = null
) {
val isServiceError: Boolean = serviceError != null
val isApplicationError: Boolean = applicationError != null
}
Below is an example of using the Service.requestRpcV2
API to handle success and failure cases:
// Fetching player data within a plugin or custom service
val getPlayerDataRequest = GetServiceV1Request.newBuilder().setPlayerId(playerId).build()
val pragmaResult = service.requestRpcV2(getPlayerDataRequest, GetServiceV1Response::parseFrom)
pragmaResult.onFailure { pragmaFailure ->
when {
pragmaFailure.isServiceError -> {
// A critical error occured - can log, rethrow, etc ...
}
pragmaFailure.isApplicationError -> {
when (pragmaFailure.applicationError!!.parse()) {
is DemoPlayerDataApplicationError -> {
/* An expected application error occured - can use data off this proto, log an error, rethrow, etc ...*/
}
else -> {
/* Some unknown application error occured - can log, rethrow, etc ... */
}
}
}
}
}
pragmaResult.onSuccess { response ->
val entities = response.playerData.entitiesList
// use entities as wanted ...
}