Idiomatic Kotlin API Guide

This document describes the idiomatic Kotlin features added to the did-core module.

Overview

The module now includes:

  • Builder DSLs for creating resolvers and registries
  • Extension functions for fluent, functional-style operations
  • Operator overloads for intuitive API usage
  • Retry logic with exponential backoff

Builder DSLs

Universal Resolver Builder

1
2
3
4
5
6
7
8
9
10
11
12
import org.trustweave.did.dsl.universalResolver

val resolver = universalResolver("https://dev.uniresolver.io") {
    timeout = 60
    apiKey = "my-api-key"
    retry {
        maxRetries = 3
        initialDelayMs = 200
        maxDelayMs = 2000
    }
    protocolAdapter = StandardUniversalResolverAdapter()
}

Registry Builder

1
2
3
4
5
6
7
import org.trustweave.did.registry.didMethodRegistry

val registry = didMethodRegistry {
    register(KeyDidMethod(kms))
    register(WebDidMethod())
    registerAll(OtherMethod1(), OtherMethod2())
}

Operator Overloads

Registry Operators

1
2
3
4
5
6
7
8
9
10
11
12
val registry = DidMethodRegistry()

// Bracket notation for access
val method = registry["key"]

// `in` operator for checking existence
if ("key" in registry) {
    // Method is registered
}

// Assignment operator
registry["new"] = NewDidMethod()

Extension Functions

DidResolutionResult extensions

DidResolutionResult is a sealed class (not Result<T>). Use when, resolveOrNull / resolveOrThrow on Did, or the small did-core helpers below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import org.trustweave.did.resolver.DidResolutionResult
import org.trustweave.did.resolver.errorCode
import org.trustweave.did.resolver.errorMessage
import org.trustweave.did.resolver.hasError
import org.trustweave.did.resolver.isNotFound
import org.trustweave.did.resolver.isSuccess

val result: DidResolutionResult = // ... from resolver.resolve(did)

// Diagnostics
if (result.isSuccess) { /* ... */ }
if (result.hasError) {
    println(result.errorMessage)
    println(result.errorCode)
}
if (result.isNotFound) { /* ... */ }

// Document extraction
val documentOrNull: DidDocument? = when (result) {
    is DidResolutionResult.Success -> result.document
    else -> null
}

val message = when (result) {
    is DidResolutionResult.Success -> "Success: ${result.document.id}"
    is DidResolutionResult.Failure -> "Failed: ${result.errorMessage}"
}

Did Extensions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.trustweave.did.identifiers.Did
import org.trustweave.did.dsl.resolveWith
import org.trustweave.did.dsl.resolveOrNull
import org.trustweave.did.dsl.resolveOrThrow
import org.trustweave.did.dsl.resolveOrDefault

val did = Did("did:key:123")
val resolver: DidResolver = // ... resolver

// Resolve to document (throws DidException on failure)
val document = did.resolveOrThrow(resolver)
val doc = did.resolveOrNull(resolver)
val docOrDefault = did.resolveOrDefault(resolver, defaultDocument)

// With callback
did.resolveWith(resolver) { document ->
    println("Resolved: ${document.id}")
}

Retry Configuration

Default Retry

1
2
3
4
val resolver = DefaultUniversalResolver(
    baseUrl = "https://dev.uniresolver.io",
    retryConfig = RetryConfig.default()  // 3 retries, 100ms initial delay
)

No Retry

1
2
3
4
val resolver = DefaultUniversalResolver(
    baseUrl = "https://dev.uniresolver.io",
    retryConfig = RetryConfig.noRetry()
)

Aggressive Retry

1
2
3
4
val resolver = DefaultUniversalResolver(
    baseUrl = "https://dev.uniresolver.io",
    retryConfig = RetryConfig.aggressive()  // 5 retries, 200ms initial delay
)

Custom Retry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
val retryConfig = RetryConfig(
    maxRetries = 5,
    initialDelayMs = 200,
    maxDelayMs = 5000,
    retryableStatusCodes = setOf(500, 502, 503, 504),
    retryableExceptions = setOf(
        java.net.ConnectException::class.java,
        java.net.SocketTimeoutException::class.java
    )
)

val resolver = DefaultUniversalResolver(
    baseUrl = "https://dev.uniresolver.io",
    retryConfig = retryConfig
)

Performance Improvements

Cached Validation

The baseUrl validation is now cached during initialization, improving performance for repeated resolutions.

Retry Logic

Automatic retry with exponential backoff for transient failures:

  • Network errors (ConnectException, SocketTimeoutException)
  • HTTP 5xx errors (configurable)
  • Exponential backoff with jitter to avoid thundering herd

Migration Guide

Before

1
2
3
4
5
6
7
8
9
10
val registry = DidMethodRegistry()
registry.register(KeyDidMethod(kms))
val method = registry.get("key")

val resolver = DefaultUniversalResolver("https://dev.uniresolver.io")
val result = resolver.resolveDid("did:key:123")
val document = when (result) {
    is DidResolutionResult.Success -> result.document
    else -> null
}

After

1
2
3
4
5
6
7
8
9
10
val registry = didMethodRegistry {
    register(KeyDidMethod(kms))
}
val method = registry["key"]  // Operator overload

val resolver = universalResolver("https://dev.uniresolver.io") {
    timeout = 60
    retry { maxRetries = 3 }
}
val document = Did("did:key:123").resolveOrNull(resolver)

Best Practices

  1. Use builder DSLs for complex configurations
  2. Use extension functions for fluent, readable code (resolveOrThrow, resolveOrNull, resolveOrDefault)
  3. Use operator overloads for intuitive API usage
  4. Configure retry logic for production environments
  5. Handle the sealed DidResolutionResult with when when you need per-failure behaviour

This site uses Just the Docs, a documentation theme for Jekyll.