David Kubecka
08/14/2024, 9:38 AMclass MetadataError : MetadataInternalError, DocumentNotFound
I think (and please correct me if I'm wrong) that this very naturally leads to union types where the domain wrapper would just be a union of the respective error components. Since Kotlin doesn't (and probably won't) have union types, what are my options? Currently, I'm doing this
interface MetadataError
interface DocumentError
class MetadataInternalError : MetadataError
class DocumentInternalError : DocumentError
class DocumentNotFound : MetadataError, DocumentError
Basically, I'm tagging each concrete error type with a list of domain types. Besides this design being sort of upside down (IMO) it leads to issues when one wants to add additional methods (such as the mentioned centralized business logging) to the domain types (they cannot have the same name because DocumentNotFound inherits from both domain types).
How to best approach this in current and possibly future Kotlin?CLOVIS
08/14/2024, 9:40 AMCLOVIS
08/14/2024, 9:44 AMinterface StandardError
interface MetadataError
interface DocumentError
class DocumentNotFound : StandardError
class MetadataInternalError : MetadataError
class MetadataDocumentError : DocumentError
context(_: Raise<StandardError>, _: Raise<MetadataError>)
fun loadMetadata(…): …
context(_: Raise<StandardError>, _: Raise<DocumentError>)
fun loadDocument(…): …
David Kubecka
08/14/2024, 9:52 AMlogMetadataError
as private fun directly inside loadMetadata
. In my case (which is slightly different) this might be ok but in general it seems to be a poor design.CLOVIS
08/14/2024, 10:00 AMDavid Kubecka
08/14/2024, 10:07 AMdata class MetadataError(val error: MetadataInternalError | DocumentNotFound) {
fun logError()
}
CLOVIS
08/14/2024, 10:09 AMsealed class MetadataError {
data class MetadataInternalError(…) : MetadataError()
data class DocumentNotFound(val error: DocumentNotFoundError) : MetadataError()
}
😕David Kubecka
08/14/2024, 10:15 AMlogError
method and as I said I cannot add it to both MetadataError
and DocumentError
because of class DocumentNotFound : MetadataError, DocumentError
. Or perhaps I'm misunderstanding your example.David Kubecka
08/14/2024, 10:16 AMphldavies
08/14/2024, 10:19 AMfun log(m: String) = println(m)
data object DocumentNotFound(val id: String)
data class MetadataInternalError(val message: String)
data class MetadataError private constructor(private val error: Any) {
constructor(dnf: DocumentNotFound): this(error = dnf)
constructor(mie: MetadataInternalError): this(error = mie)
fun logError(): Unit = when(error) {
is DocumentNotFound -> log("Missing document id=${error.id}")
is MetadataInternalError -> log("Failed to retrieve metadata: ${error.message}"
else -> error("unexpected error type")
}
}
phldavies
08/14/2024, 10:23 AMlogError
why not lift to a common interface for both error interfaces?phldavies
08/14/2024, 10:23 AMinterface LoggableError {
fun logError()
}
interface MetadataError : LoggableError
interface DocumentError : LoggableError
class MetadataInternalError : MetadataError {
override fun logError() = println("I am MetadataInternalError")
}
class DocumentInternalError : DocumentError{
override fun logError() = println("I am DocumentInternalError")
}
class DocumentNotFound : MetadataError, DocumentError {
override fun logError() = println("I am DocumentNotFound")
}
fun main() {
MetadataInternalError().logError()
DocumentInternalError().logError()
DocumentNotFound().logError()
}
David Kubecka
08/14/2024, 10:25 AMit's horribleOf course, with unsafe typing you can do many things but that's not what we are after 🙂
phldavies
08/14/2024, 10:26 AMDavid Kubecka
08/14/2024, 10:28 AMwhy not lift to a common interface for both error interfacesBecause the method should be specific to the domain type, not the concrete errors. Ideally:
union class MetadataError {
MetadataInternalError, DocumentNotFound; // similar syntax as for enums
fun logError() = println("I am MetadataError")
}
union class DocumentError {
DocumentInternalError, DocumentNotFound; // similar syntax as for enums
fun logError() = println("I am DocumentError")
}
David Kubecka
08/14/2024, 10:30 AMarguably the unsafe typing is constrained within the type itselfI understand that. My mind just somehow refuses to reimplement union types mechanism in each place where it's needed 🙂
phldavies
08/14/2024, 10:30 AMsealed interface MetadataError
sealed interface DocumentError
class MetadataInternalError : MetadataError
class DocumentInternalError : DocumentError
class DocumentNotFound : MetadataError, DocumentError
fun MetadataError.logError() = println("MetadataError:$this")
fun DocumentError.logError() = println("DocumentError:$this")
fun main() {
MetadataInternalError().logError()
DocumentInternalError().logError()
// DocumentNotFound().logError() // ambiguous
(DocumentNotFound() as DocumentError).logError()
(DocumentNotFound() as MetadataError).logError()
}
extension methods maybe?phldavies
08/14/2024, 10:31 AMDavid Kubecka
08/14/2024, 10:35 AMDavid Kubecka
08/14/2024, 10:45 AMdata class
is mostly a code generation tool to hide boilerplate"CLOVIS
08/14/2024, 12:37 PMCLOVIS
08/14/2024, 12:38 PMDavid Kubecka
08/14/2024, 12:49 PMunion class
proposal would be feasible?CLOVIS
08/14/2024, 12:49 PMCLOVIS
08/14/2024, 12:50 PM