grpc-kotlin
grpc-kotlin copied to clipboard
Add a way to get the service descriptor from the stub/implbase
Right now, it is challenging to reach the service descriptor from the stub in a generic way, and the stub is the main class that is used in the client code.
Our use case is to read some custom options set at the service level.
It is possible to jump from the stub to the service kotlin class using reflection and leveraging stub::class.java.enclosingClass or jumping to the java class by looking for the StubFor annotation. Still, both are implementation details that could change in the future. From there, getting the descriptor also requires reflection, as none of the Kotlin or Java classes implement an interface.
Maybe I am missing some helper functions, but this is the hacky code I needed to read an option from the service:
private fun <S : AbstractCoroutineStub<*>> getHost(stub: KClass<S>): String {
val serviceClass = (stub.annotations.firstOrNull { it.annotationClass == StubFor::class } as StubFor?)?.value
checkNotNull(serviceClass) { "service class not found from the stub" }
val serviceDescriptorMethod = serviceClass.members.firstOrNull {
it.name == "getServiceDescriptor" &&
it.parameters.isEmpty() &&
it.returnType.classifier == ServiceDescriptor::class
}
checkNotNull(serviceDescriptorMethod) { "service descriptor not found from the stub" }
val schemaDescriptor = (serviceDescriptorMethod.call() as ServiceDescriptor).schemaDescriptor
check(schemaDescriptor is ProtoServiceDescriptorSupplier) { "service descriptor is not a ProtoServiceDescriptor" }
val serviceOptions = schemaDescriptor.serviceDescriptor.options
val option = serviceOptions.getExtension(OptionClass.option)
From the implbase we don't even have an annotation to leverage, so we are limited to use the enclosing path.
In Java, this is easier as both impl and stub are built from the service class; still, it does not implement any interface, so it is difficult to access the get descriptor method without reflection, but you don't need additional hoops.
My suggestion would be to add accessors from the stub/impl to get the ServiceGrpcKt class and make all ServiceGrpcKt classes implement an interface like this:
interface ServiceDescriptorInterface {
fun getDescriptor(): ServiceDescriptor
}
With that interface, you can access the name, the methods, and the service itself.