Search code examples
kotlinenumssubclasssubtype

How to restrict enums in Kotlin?


I have an enum with many values; error codes for example, or some official list of coded values. In my application, I have several functions where only a subset of those values is admissible. How can I derive restricted enums that contain only a subset of the original enum?

For example, I have an externally provided dictionary of error codes that model as enum:

enum class ApiError(val: errorCode: Int) {
  INCORRECT_CHARACTER(1),
  MISSING_VALUE(2),
  TOO_SMALL(3),
  TOO_LARGE(4)
}

In one function call, only the TOO_SMALL and TOO_LARGE errors may result, in another only INCORRECT_CHARACTER or MISSING_VALUE. Instead of defining two new enums for these particular error return values, I would like both to somehow reference the complete enum with all error codes.

To be more precise: Assume I have a function fun handleError(error: ApiError); inside this function, I want to be able to write an exhaustive when pattern match that covers all enum cases. However, I also want to be able to pass an argument of a restricted enum type to that same function, where that restricted type can take on only a subset of the enum values, as in the example above.

What comes to mind (but does not work in Kotlin) would be to subclass the ApiError enum while restricting the admissible values in each subclass. Is there a Kotlin solution that does something similar?

The opposite question – to subclass an enum for extension – has been discussed here at length. As far as I understand, the objections there do not apply when restricting the potential enum values.

And just for curiosity: I suppose the above question is some concrete and utterly misspecified version of a some type theoretical problem. Can someone provide pointers to the proper theory and terminology?


Solution

  • What comes to mind (but does not work in Kotlin) would be to subclass the APIError enum while restricting the admissible values in each subclass. Is there a Kotlin solution that does something similar?

    Yes, if you need to express a hierarchy, you could use sealed class/interface hierarchies with objects as leaves.

    sealed class ApiError(val code: Int) {
        object IncorrectCharacter : ApiError(1)
        object MissingValue : ApiError(2)
    }
    sealed class SizeError(code: Int): ApiError(code) {
        object TooSmall : SizeError(3)
        object TooLarge : SizeError(4)
    }
    

    What you lose here compared to enums is the ability to list all possible values using ApiError.values(). But in this case it might not be an issue.

    Also it might not be ideal to serialize (and even more so, deserialize), depending on which serialization library you're using.