Error Handling Patterns: Exceptions vs Sealed Results
TrustWeave uses a hybrid error handling approach: exceptions for programming errors and sealed results for expected failures. This guide explains when to use each pattern and why.
Sealed Results → Expected failures that are part of normal operation
Exception-Based Operations
Operations that throw exceptions are those where failure indicates a programming error or invalid configuration:
DID Creation (createDid())
Why Exceptions? Creating a DID should always succeed if configuration is correct. Failure means:
Invalid configuration (programming error)
Missing required components (KMS not configured)
Method not registered (should be registered at startup)
1
2
3
4
5
6
7
8
9
10
11
try{valdid=trustweave.createDid()// Success - DID created}catch(error:DidException.DidMethodNotRegistered){// Programming error: Method should be registeredprintln("Method not registered: ${error.method}")println("Available: ${error.availableMethods}")}catch(error:DidException){// Configuration errorprintln("Failed to create DID: ${error.message}")}
Credential Issuance (issueCredential())
Why Exceptions? Credential issuance should succeed if:
Operations that return sealed results are those where failure is expected and normal:
DID Resolution (resolveDid())
Why Sealed Result? DID resolution can fail for expected reasons:
DID doesn’t exist (normal - DIDs are created by others)
Network unavailable (expected transient failure)
Method not available (expected - methods are optional)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
when(valresult=trustweave.resolveDid("did:key:z6Mk...")){isDidResolutionResult.Success->{println("Resolved: ${result.document.id}")}isDidResolutionResult.Failure.NotFound->{// Expected: DID doesn't exist yetprintln("DID not found - may be created later")}isDidResolutionResult.Failure.NetworkError->{// Expected: Network may be temporarily unavailableprintln("Network error - retry later")}isDidResolutionResult.Failure.MethodNotRegistered->{// Expected: Method may not be availableprintln("Method not available: ${result.method}")}// Compiler ensures all cases handled}
Credential Verification (verifyCredential())
Why Sealed Result? Verification can fail for expected reasons:
when(valresult=trustweave.verifyCredential(credential)){isCredentialVerificationResult.Valid->{println("✅ Valid: ${result.credential.id}")result.warnings.forEach{println("⚠️ Warning: $it")}}isCredentialVerificationResult.Invalid.Expired->{// Expected: Credentials expireprintln("❌ Expired at ${result.expiredAt}")}isCredentialVerificationResult.Invalid.Revoked->{// Expected: Credentials can be revokedprintln("❌ Revoked at ${result.revokedAt}")}isCredentialVerificationResult.Invalid.InvalidProof->{// Expected: Proof may be invalidprintln("❌ Invalid proof: ${result.reason}")}isCredentialVerificationResult.Invalid.UntrustedIssuer->{// Expected: Policy decisionprintln("❌ Issuer not trusted: ${result.issuer}")}// Compiler ensures all cases handled}
// ❌ Bad: Using sealed result for programming errorvaldidResult=trustweave.createDid()when(didResult){isSuccess->{...}isFailure->{...}// This is a programming error, not expected}// ✅ Good: Exception for programming errortry{valdid=trustweave.createDid()// Should succeed if configured}catch(error:DidException.DidMethodNotRegistered){// Fix configurationthrowIllegalStateException("Missing DID method registration",error)}
Pattern 2: Sealed Result for Expected Failures
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ❌ Bad: Using exception for expected failuretry{valresolution=trustweave.resolveDid("did:unknown:test")}catch(error:DidException.DidNotFound){// This is expected - DID may not exist}// ✅ Good: Sealed result for expected failurewhen(valresolution=trustweave.resolveDid("did:unknown:test")){isDidResolutionResult.Success->{// DID exists}isDidResolutionResult.Failure.NotFound->{// Expected: DID doesn't existprintln("DID not found - this is normal")}}
When to Use Each Pattern
Use Exceptions When:
Programming Error
Method not registered (should be registered at startup)
// ✅ Good: Handle exceptions for configuration errorstry{valdid=trustweave.createDid()// Exception on config error}catch(error:DidException){// Log and fix configurationlogger.error("DID creation failed",error)throwIllegalStateException("Configuration error",error)}// ✅ Good: Handle sealed results for expected failureswhen(valverification=trustweave.verifyCredential(credential)){isCredentialVerificationResult.Valid->{// Proceed}isCredentialVerificationResult.Invalid->{// Handle expected failure gracefullylogger.warn("Credential invalid: ${verification.errors}")// Show user-friendly message}}
2. Don’t Mix Patterns Incorrectly
1
2
3
4
5
6
7
8
9
10
11
12
13
// ❌ Bad: Treating expected failure as exceptiontry{valresolution=trustweave.resolveDid("did:unknown:test")// This can fail normally - shouldn't be exception}catch(error:Exception){// Wrong pattern}// ✅ Good: Using sealed result for expected failurewhen(valresolution=trustweave.resolveDid("did:unknown:test")){isDidResolutionResult.Success->{...}isDidResolutionResult.Failure->{...}// Expected}
3. Use Exhaustive Handling for Sealed Results
1
2
3
4
5
6
7
8
9
10
// ✅ Good: Compiler ensures all cases handledwhen(valresult=trustweave.resolveDid(did)){isDidResolutionResult.Success->{...}isDidResolutionResult.Failure.NotFound->{...}isDidResolutionResult.Failure.MethodNotRegistered->{...}isDidResolutionResult.Failure.InvalidFormat->{...}isDidResolutionResult.Failure.NetworkError->{...}isDidResolutionResult.Failure.ResolutionError->{...}// Compiler error if case missing}
Quick Reference
Operation
Pattern
Return Type
Example
createDid()
Exception
DidDocument
try { ... } catch { ... }
resolveDid()
Sealed Result
DidResolutionResult
when (result) { ... }
issueCredential()
Exception
VerifiableCredential
try { ... } catch { ... }
verifyCredential()
Sealed Result
CredentialVerificationResult
when (result) { ... }
createWallet()
Exception
Wallet
try { ... } catch { ... }
blockchains.anchor()
Exception
AnchoredData
try { ... } catch { ... }
blockchains.read()
Exception
T
try { ... } catch { ... }
Summary
Exceptions = Programming errors, configuration issues (should be fixed)
Sealed Results = Expected failures, business logic decisions (handle gracefully)
This hybrid approach provides:
✅ Clear error types for debugging (exceptions)
✅ Exhaustive handling for business logic (sealed results)