API design for `connection` style resource

Hey gents, I’m designing an interprocess thing for our application and I’ve got it working pretty well, but I’m wondering about the exact semantics of connections.

given the classes:

class SessionFactory {
  fun createSession(someConfig: Config): Session
}
class Session: Closeable {
  fun exchange(request: Message): /*response:*/ Message 
  //a couple other methods for fire-and-forget functionality
}

in point of fact, our actual API has a boatload of suspend’s in it, but I dont think that’s relevant here.

My happy-path code can then look like

@UIFramework fun onSyncButtonClick(event: Event){
  val uiRelatedValues = sessionFactory.create(config).use { session -> 
     //suspending code block that hits this device a lot and builds useful information for this view.
  }
}

which is all very nifty, except I’m wondering what the best way to express an error here.

I could have the return type from SessionFactory.create() be a sealed class with one of those sealed classes being some kind of error wrapper. The idiom then becomes

class SessionFactory {
  fun createSession(someConfig: Config): SessionOrError
}
sealed class SessionOrError: Closeable {
  data class Error(val cause: ProblemDescription): SessionOrError {
    override fun close(){}
  }
  class Session: SessionOrError {
    fun exchange(request: Message): Message
    //...
  }
}

@UIFramework fun onSyncButtonClick(event: Event){
  val uiRelatedValues = sessionFactory.create(config).use { sessionOrError -> 
     when(sessionOrError){
       is Error -> doPopupOrLogOrSomethingClever(sessionOrError.error)
       is ValidSession -> {
         //previous code block
       }
     }
     //could also reduce indentation with 
     val session = if(sessionOrError is Error){
       doPopupOrLogOrSomethingClever(sessionOrError.error)
       return@use
     } else (sessionOrError as Session).session
  }
}

I could use funktional’s either type:

class SessionFactory {
  fun createSession(someConfig: Config): Either<Session, Error>
}
@UIFramework fun onSyncButtonClick(event: Event){
  val uiRelatedValues = sessionFactory.create(config).mapLeft { error ->
    doPopupOrLogOrSomethingClever(error)
  }
  uiRelatedValues.mapRight { it.use { session -> 
    //...
  }}
}

I suppose I could also up the arity and proivde a custom use method, such that we get something like

@UIFramework fun onSyncButtonClick(event: Event){
  val uiRelatedValues = sessionFactory.create(config).customUse { session, error -> 
     if(error != null){
       doPopup(error)
     }
     else if (session != null){
       //...
     }
  }
}

if you were a dev on my team, what would you like to see? what would you hate to see? How comfortable are you with something like an Either type?

The first thing that comes to mind when you’re dealing with errors is good old exception. I would definitely use exceptions for error handling in this case.