GithubHelp home page GithubHelp logo

marcoferrer / kroto-plus Goto Github PK

View Code? Open in Web Editor NEW
495.0 24.0 28.0 1.2 MB

gRPC Kotlin Coroutines, Protobuf DSL, Scripting for Protoc

License: Apache License 2.0

Kotlin 100.00%
coroutines kotlin grpc protobuf protocol-buffers kotlin-coroutines grpc-java code-generation protoc-plugin protoc-grpc-plugin kotlin-script grpc-kotlin

kroto-plus's Introduction

Kroto+

gRPC Kotlin Coroutines, Protobuf DSL, Scripting for Protoc

Build Status GitHub license JCenter Maven Central Awesome Kotlin Badge Awesome gRPC Slack

Community Contributions are Welcomed

ℹ️ | Docs are being expanded and moved to Readme.io

Quick Start: gRPC Coroutines

Run the following command to get started with a preconfigured template project. (kotlin-coroutines-gRPC-template)

git clone https://github.com/marcoferrer/kotlin-coroutines-gRPC-template && \
cd kotlin-coroutines-gRPC-template && \
./gradlew run 

Getting Started

Code Generators


Proto Builder Generator (Message DSL)

This generator creates lambda based builders for message types

     val starPlatinum = Stand {
        name = "Star Platinum"
        powerLevel = 500
        speed = 550
        attack {
            name = "ORA ORA ORA"
            damage = 100
            range = Attack.Range.CLOSE
        }
    }
    
    val attack = Attack {
        name = "ORA ORA ORA"
        damage = 100
        range = Attack.Range.CLOSE
    }
    
    // Copy extensions
    val newAttack = attack.copy { damage = 200 }            
    
    // orDefault() will return the messages default instance when null
    val nullableAttack: Attack? = null
    nullableAttack.orDefault()
    
    // Plus operator extensions 
    val mergedAttack = attack + Attack { name = "Sunlight Yellow Overdrive" }            

gRPC Coroutines Client & Server    codecov

This option requires the artifact kroto-plus-coroutines as a dependency.

Client / Server Examples
Method Signature Option Support

  • Design
  • Client Stubs
    • Designed to work well with Structured Concurrency
    • Cancellation of the client CoroutineScope will propagate to the server.
    • Cancellations can now be propagated across usages of a specific stub instance.
    • Rpc methods are overloaded with inline builders for request types
    • The request parameter for rpc methods defaults to RequestType.defaultsInstance
// Creates new stub with a default coroutine context of `EmptyCoroutineContext`
val stub = GreeterCoroutineGrpc.newStub(channel)

// Suspends and creates new stub using the current coroutine context as the default. 
val stub = GreeterCoroutineGrpc.newStubWithContext(channel)

// An existing stub can replace its current coroutine context using either 
stub.withCoroutineContext()
stub.withCoroutineContext(coroutineContext)

// Stubs can accept message builder lambdas as an argument  
stub.sayHello { name = "John" }

// For more idiomatic usage in coroutines, stubs can be created
// with an explicit coroutine scope using the `newGrpcStub` scope extension function.
launch {
    // Using `newGrpcStub` makes it clear that the resulting stub will use the receiving 
    // coroutine scope to launch any concurrent work. (usually for manual flow control in streaming apis) 
    val stub = newGrpcStub(GreeterCoroutineGrpc.GreeterCoroutineStub, channel)
    
    val (requestChannel, responseChannel) = stub.sayHelloStreaming()
    ...
}

  • Service Base Impl
    • Rpc calls are wrapped within a scope initialized with the following context elements.
      • CoroutineName set to MethodDescriptor.fullMethodName
      • GrpcContextElement set to io.grpc.Context.current()
    • Base services implement ServiceScope and allow overriding the initial coroutineContext used for each rpc method invocation.
    • Each services initialContext defaults to EmptyCoroutineContext
    • A common case for overriding the initialContext is for setting up application specific ThreadContextElement or CoroutineDispatcher, such as MDCContext() or newFixedThreadPoolContext(...)

Cancellation Propagation

  • Client
    • Both normal and exceptional coroutine scope cancellation will cancel the underlying call stream. See ClientCall.cancel() in io.grpc.ClientCall.java for more details.
    • In the case of service implementations using coroutines, this client call stream cancellation will cancel the coroutine scope of the rpc method being invoked on the server.
  • Server
    • Exceptional cancellation of the coroutine scope for the rpc method will be mapped to an instance of StatusRuntimeException and returned to the client.
    • Normal cancellation of the coroutine scope for the rpc method will be mapped to an instance of StatusRuntimeException with a status of Status.CANCELLED, and returned to the client.
    • Cancellation signals from the corresponding client will cancel the coroutine scope of the rpc method being invoked.

Examples

Unary

Client: Unary calls will suspend until a response is received from the corresponding server. In the event of a cancellation or the server responds with an error the call will throw the appropriate StatusRuntimeException

val response = stub.sayHello { name = "John" }

Server: Unary rpc methods can respond to client requests by either returning the expected response type, or throwing an exception.

override suspend fun sayHello(request: HelloRequest): HelloReply {

    if (isValid(request.name))
        return HelloReply { message = "Hello there, ${request.name}!" } else
        throw Status.INVALID_ARGUMENT.asRuntimeException()
}

Client Streaming

Client: requestChannel.send() will suspend until the corresponding server signals it is ready by requesting a message. In the event of a cancellation or the server responds with an error, both requestChannel.send() and response.await(), will throw the appropriate StatusRuntimeException.

val (requestChannel, response) = stub.sayHelloClientStreaming()

launchProducerJob(requestChannel){
    repeat(5){
        send { name = "name #$it" }
    }
}

println("Client Streaming Response: ${response.await()}")

Server: Client streaming rpc methods can respond to client requests by either returning the expected response type, or throwing an exception. Calls to requestChannel.receive() will suspend and notify the corresponding client that the server is ready to accept a message.

override suspend fun sayHelloClientStreaming(
    requestChannel: ReceiveChannel<HelloRequest>
): HelloReply =  HelloReply {
    message = requestChannel.toList().joinToString()
}

Server Streaming

Client: responseChannel.receive() will suspend and notify the corresponding server that the client is ready to accept a message.

val responseChannel = stub.sayHelloServerStreaming { name = "John" }

responseChannel.consumeEach {
    println("Server Streaming Response: $it")
}

Server: Server streaming rpc methods can respond to client requests by submitting messages of the expected response type to the response channel. Completion of service method implementations will automatically close response channels in order to prevent abandoned rpcs.

Calls to responseChannel.send() will suspend until the corresponding client signals it is ready by requesting a message. Error responses can be returned to clients by either throwing an exception or invoking close on responseChannel with the desired exception.

For an example of how to implement long lived response streams please reference MultipleClientSubscriptionsExample.kt.

override suspend fun sayHelloServerStreaming(
    request: HelloRequest,
    responseChannel: SendChannel<HelloReply>
) {        
    for(char in request.name) {
        responseChannel.send {
            message = "Hello $char!"
        }
    }
}

Bi-Directional Streaming

Client: requestChannel.send() will suspend until the corresponding server signals it is ready by requesting a message. In the event of a cancellation or the server responds with an error, both requestChannel.send() and response.await(), will throw the appropriate StatusRuntimeException.

val (requestChannel, responseChannel) = stub.sayHelloStreaming()

launchProducerJob(requestChannel){
    repeat(5){
        send { name = "person #$it" }
    }
}

responseChannel.consumeEach {
    println("Bidi Response: $it")
}

Server: Bidi streaming rpc methods can respond to client requests by submitting messages of the expected response type to the response channel. Completion of service method implementations will automatically close response channels in order to prevent abandoned rpcs.

Calls to responseChannel.send() will suspend until the corresponding client signals it is ready by requesting a message. Error responses can be returned to clients by either throwing an exception or invoking close on responseChannel with the desired exception.

For an example of how to implement long lived response streams please reference MultipleClientSubscriptionsExample.kt.

override suspend fun sayHelloStreaming(
    requestChannel: ReceiveChannel<HelloRequest>,
    responseChannel: SendChannel<HelloReply>
) {
    requestChannel.mapTo(responseChannel){
    
        HelloReply {
            message = "Hello there, ${it.name}!"
        }
    }
}

gRPC Stub Extensions

This modules generates convenience extensions that overload the request message argument for rpc methods with a builder lambda block and a default value. It also supports generating overloads based off (google.api.method_signature) method options. More info available here

               
    //Kroto+ Generated Extension
    val response = serviceStub.myRpcMethod {
         id = 100
         name = "some name"
    }

    //Original Java Fluent builders
    val response = serviceStub.myRpcMethod(ExampleServiceGrpc.MyRpcMethodRequest
        .newBuilder()
        .setId(100)
        .setName("some name")
        .build())                  

For unary rpc methods, the generator will create the following extensions

    //Future Stub with default argument
    fun ServiceBlockingStub.myRpcMethod(request: Request = Request.defaultInstance): ListenableFuture<Response>
    
    //Future Stub with builder lambda 
    inline fun ServiceFutureStub.myRpcMethod(block: Request.Builder.() -> Unit): ListenableFuture<Response>
        
    //Blocking Stub with default argument
    fun ServiceBlockingStub.myRpcMethod(request: Request = Request.defaultInstance): Response
    
    //Blocking Stub with builder lambda
    inline fun ServiceBlockingStub.myRpcMethod(block: Request.Builder.() -> Unit): Response 

Coroutine Support

In addition to request message arguments as builder lambda rpc overloads, coroutine overloads for rpc calls can also be generated. This provides the same functionality as the generated coroutine stubs. Usage is identical to the client examples outlined in Coroutine Client Examples.

  • This is accomplished by defining extension functions for async service stubs.
  • This option requires the artifact kroto-plus-coroutines as a dependency.
  • If using rpc interceptors or other code that relies on io.grpc.Context then you need to be sure to add a GrpcContextElement to your CoroutineContext when launching a coroutine. Child coroutines will inherit this ThreadContextElement and the dispatcher will ensure that your grpc context is present on the executing thread.
    Context.current().withValue(MY_KEY, myValue).attach()
    
    val myGrpcContext = Context.current()
    
    val job = launch( GrpcContextElement() ) { //Alternate usage:  myGrpcContext.asContextElement() 
       
        launch {
            assertEquals(myGrpcContext, Context.current())
        }
       
        GlobalScope.launch{
            assertNotEquals(myGrpcContext, Context.current())
        } 
    }

Mock Service Generator

This generator creates mock implementations of proto service definitions. This is useful for orchestrating a set of expected responses, aiding in unit testing methods that rely on rpc calls. Full example for mocking services in unit tests. The code generated relies on the kroto-plus-test artifact as a dependency. It is a small library that provides utility methods used by the mock services.

  • If no responses are added to the response queue then the mock service will return the default instance of the response type.
  • Currently only unary methods are being mocked, with support for other method types on the way
@Test fun `Test Unary Response Queue`(){
    
    MockStandService.getStandByNameResponseQueue.apply {
       //Queue up a valid response message
       addMessage {
           name = "Star Platinum"
           powerLevel = 500
           speed = 550
           addAttacks {
               name = "ORA ORA ORA"
               damage = 100
               range = StandProto.Attack.Range.CLOSE
           }
       }   
          
       //Queue up an error
       addError(Status.INVALID_ARGUMENT)
   }
   
   val standStub = StandServiceGrpc.newBlockingStub(grpcServerRule.channel)
   
   standStub.getStandByName { name = "Star Platinum" }.let{ response ->
       assertEquals("Star Platinum",response.name)
       assertEquals(500,response.powerLevel)
       assertEquals(550,response.speed)
       response.attacksList.first().let{ attack ->
           assertEquals("ORA ORA ORA",attack.name)
           assertEquals(100,attack.damage)
           assertEquals(StandProto.Attack.Range.CLOSE,attack.range)
       }
   }
   
   try{
       standStub.getStandByName { name = "The World" }
       fail("Exception was expected with status code: ${Status.INVALID_ARGUMENT.code}")
   }catch (e: StatusRuntimeException){
       assertEquals(Status.INVALID_ARGUMENT.code, e.status.code)
   }
}

Extendable Messages Generator (Experimental)

Generated code relies on the kroto-plus-message artifact. This generator adds tagging interfaces to the java classes produce by protoc. It also adds pseudo companion objects to provide a way to access proto message APIs in a non static manner. The following is a small example of how to write generic methods and extensions that resolve both message and builders type.

inline fun <reified M, B> M.copy( block: B.() -> Unit ): M
        where M : KpMessage<M, B>, B : KpBuilder<M> {
    return this.toBuilder.apply(block).build()
}

// Usage
myMessage.copy { ... }

inline fun <reified M, B> build( block: B.() -> Unit ): M
        where M : KpMessage<M, B>, B : KpBuilder<M> {

    return KpCompanion.Registry[M::class.java].build(block)
}

// Usage
build<MyMessage> { ... }

inline fun <M, B> KpCompanion<M, B>.build( block: B.() -> Unit ): M
        where B : KpBuilder<M>,M : KpMessage<M,B> {

    return newBuilder().apply(block).build()
}

// Usage
MyMessage.Companion.build { ... }

User Defined Generator Scripts

Users can define kotlin scripts that they would like to run during code generation. For type completion, scripts can be couple with a small gradle build script, although this is completely optional. Samples are available in the kp-script directory of the example project.

There are two categories of scripts available.

    • Configuration Options
    • Using the insertion api from the java protoc plugin, users can add code at specific points in generated java classes.
    • This is useful for adding code to allow more idiomatic use of generated java classes from Kotlin.
    • The entire ExtendableMessages generator can be implemented using an insertion script, an example can be in the example script extendableMessages.kts.
    • Additional information regarding the insertion api can be found in the official docs
    • Configuration Options
    • These scripts implement the Generator interface used by all internal kroto+ code generators.
    • Generators rely on the GeneratorContext, which is available via the property context.
    • The context is used for iterating over files, messages, and services submitted by protoc.
    • Example usage can be found in the kp-script directory of the example project, as well as inside the generators package of the protoc-gen-kroto-plus artifact.

Community Scripts

Community contributions for scripts are welcomed and more information regarding guidelines will be published soon.


Method Signature Options Support

Usage of (google.api.method_signature) method option is now supported. This allows users to customize the method parameters outputted in generated clients as well as stub extensions. To config your rpc methods, first add the google common proto dependency to your build

dependencies{
    compileOnly "com.google.api.grpc:proto-google-common-protos:1.16.0"
}

Then add the following import to your proto definition.

import "google/api/client.proto";

Now the method option should be available for usage in your method definition

// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply){
    option (google.api.method_signature) = "name";
};

This will result in the following method signature being outputed from gRPC and stub extension code generators.

 fun GreeterStub.sayHello(name: String): HelloReply{
    val request = HelloRequest.newBuilder()
        .setName(name)
        .build()
    return sayHello(request)
 }

Getting Started With Gradle

Repositories

  • Available on jcenter() or mavenCentral()
  • SNAPSHOT
repositories {
    maven { url 'https://oss.jfrog.org/artifactory/oss-snapshot-local' }
}
  • Bintray
// Useful when syncronization to jcenter or maven central are taking longer than expected
repositories {
    maven { url 'https://dl.bintray.com/marcoferrer/kroto-plus/' }
}
Configuring Protobuf Gradle Plugin
plugins{
    id 'com.google.protobuf' version '0.8.6'
}

protobuf {
    protoc { artifact = "com.google.protobuf:protoc:$protobufVersion"}

    plugins {
        kroto {
            artifact = "com.github.marcoferrer.krotoplus:protoc-gen-kroto-plus:$krotoPlusVersion"
        }
    }

    generateProtoTasks {
        def krotoConfig = file("krotoPlusConfig.asciipb") // Or .json

        all().each{ task ->
            // Adding the config file to the task inputs lets UP-TO-DATE checks
            // include changes to configuration
            task.inputs.files krotoConfig

            task.plugins {
                kroto {
                    outputSubDir = "java"
                    option "ConfigPath=$krotoConfig"
                }
            }
        }
    }
}

Getting Started With Maven

Repositories

  • Available on jcenter or mavenCentral
  • SNAPSHOT
<repository>
    <id>oss-snapshot</id>
    <name>OSS Snapshot Repository</name>
    <url>https://oss.jfrog.org/artifactory/oss-snapshot-local</url>
    <snapshots>
        <enabled>true</enabled>
    </snapshots>
</repository>
  • Bintray
<!-- Useful when syncronization to jcenter or maven central are taking longer than expected-->
<repository>
    <id>kroto-plus-bintray</id>
    <name>Kroto Plus Bintray Repository</name>
    <url>https://dl.bintray.com/marcoferrer/kroto-plus/</url>
</repository>
Configuring Protobuf Maven Plugin
<plugin>
    <groupId>org.xolstice.maven.plugins</groupId>
    <artifactId>protobuf-maven-plugin</artifactId>
    <version>0.6.1</version>
    <configuration>
        <protocArtifact>com.google.protobuf:protoc:3.6.1:exe:${os.detected.classifier}</protocArtifact>
    </configuration>
    <executions>
        <execution>
            <goals><goal>compile</goal></goals>
        </execution>
        <execution>
            <id>grpc-java</id>
            <goals><goal>compile-custom</goal></goals>
            <configuration>
                <pluginId>grpc-java</pluginId>
                <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.17.1:exe:${os.detected.classifier}</pluginArtifact>
            </configuration>
        </execution>
        <execution>
            <id>kroto-plus</id>
            <goals>
                <goal>compile-custom</goal>
            </goals>
            <configuration>
                <pluginId>kroto-plus</pluginId>
                <pluginArtifact>com.github.marcoferrer.krotoplus:protoc-gen-kroto-plus:${krotoPlusVersion}:exe:${os.detected.classifier}</pluginArtifact>
                <pluginParameter>ConfigPath=${project.basedir}/krotoPlusConfig.asciipb</pluginParameter>
            </configuration>
        </execution>
    </executions>
</plugin>

Add generated sources to Kotlin plugin

<plugin>
    <artifactId>kotlin-maven-plugin</artifactId>
    <groupId>org.jetbrains.kotlin</groupId>
    <version>${kotlin.version}</version>
    <executions>
        <execution>
            <id>compile</id>
            <goals>
                <goal>compile</goal>
            </goals>
            <configuration>
                <sourceDirs>
                    <sourceDir>${project.basedir}/target/generated-sources/protobuf/kroto-plus</sourceDir>
                </sourceDirs>
            </configuration>
        </execution>
    </executions>
</plugin>

Configuring Generators

Credit

This project relies on Kotlin Poet for building Kotlin sources. A big thanks to all of its contributors.

kroto-plus's People

Contributors

marcoferrer avatar qsona avatar sauldhernandez avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

kroto-plus's Issues

Disabled dsl marker insertions are still emitted

A bug was introduced where dsl insertions are still being generated even when the flag is disabled. This issue was reported originally on the Kotlin kroto+ slack channel by minisu link.

This primarily effects maven users

Use kroto-plus as protoc plugin

Hey !

I would like to know if this repo can be used as plugin for protoc? In fact I would like to generate the sources manually and using protoc, is that possible?

Thanks

Update method signature support to adhere to spec

The current implementation of support for method signature options is compliant with the specification defined in googleapis/api-common-protos/client.proto#L79

The following changes need to be made to become spec compliant.

  • Each instance of a method signature annotation is to be treated as a target signature
  • The fields to include in the signature are to be defined as a comma delimited string

The current implementation incorrectly assumes that there is only ever one target method signature to implement. It also collects all of the signature annotation entries on a method and treats them as the fields to output in the target signature.

Check client connection status in Server

Hello, In bidirectional streaming How I can check client connection status? I am developing multiplayer online game with your library and I want to know when one player disconnect and notify to other players?

Support kotlin @DslMarker annotation for proto builders

Since the generator for proto builders is essentially creating a dsl for the proto messages, it would benefit from the safety provided by the @DslMarker annotation.

In order to support this we'll have to use the protobuf insertion points to tag the builder interfaces with this required annotation.

This feature will have to be disabled by default. Users will then need to opt in explicitly since it requires the plugin output path to be the same as the java sources. Ive successfully tested a working prototype and just need to get the code generation wrapped up.

Adding Kroto Plus to a project with Springfox Swagger2 causes infinite loop before start

Arrangement:
Project with a Spring Web MVC @RestController that uses a protobuf generated class as an input, and calls the same server code as the @GrpcSpringService (using https://github.com/yidongnan/grpc-spring-boot-starter). Kroto Plus configured to generate builders, message extensions, and coroutines for the service and messages defined in the proto files.

Kroto Plus version 0.6.0-SNAPSHOT was used

/*
 * Copyright (C) 2019 Electronic Arts Inc. All rights reserved.
 */

package com.ea.poutine.roolz.rlzservice

import com.ea.p.r.r.api.RController
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.ComponentScan
import springfox.documentation.builders.PathSelectors
import springfox.documentation.builders.RequestHandlerSelectors
import springfox.documentation.spi.DocumentationType
import springfox.documentation.spring.web.plugins.Docket
import springfox.documentation.swagger2.annotations.EnableSwagger2

@SpringBootApplication
@EnableSwagger2
// import the specific ones we want Swagger exposed for, rather than let it wander around scanning controllers in the packages
@ComponentScan(basePackageClasses = [RController::class])
class Swagger2SpringBoot {
    companion object {
        lateinit var appContext: ConfigurableApplicationContext

        @JvmStatic
        fun main(args: Array<String>) {
            appContext = SpringApplication.run(Swagger2SpringBoot::class.java, *args)
        }
    }

    @Bean
    fun api(): Docket {
        return Docket(DocumentationType.SWAGGER_2)
            .select()
            .apis(RequestHandlerSelectors.any())
            .paths(PathSelectors.any())
            .build()
    }
}
@SpringBootApplication
@ComponentScan("com.ea.p")
@EnableDiscoveryClient
@EnableConfigurationProperties
class RApp {
    companion object {
        private val logger = KotlinLogging.logger { }

        @JvmStatic
        fun main(args: Array<String>) {
            logger.debug { "Running RApplication with args: ${Klaxon().toJsonString(args)}" }
            SpringApplication.run(RApp::class.java, *args)
        }

        @Bean
        fun init() = CommandLineRunner {
            logger.debug { "init called" }
        }

        @Bean
        fun userProvider(): UserProvider {
            return ThreadLocalUserProvider()
        }
    }
}
@RestController
@RequestMapping("/v3/r")
class RController @Autowired constructor(
    private val service: RService,
    fConverter: FConverter,
    private val timers: Timers,
    private val rResultConverter: rResultConverter,
    private val container: KContainer
) {
    private val bEvaluator = BEvaluator(service, fConverter)
    private val logger = KotlinLogging.logger { }

    @PostMapping(value = ["/tenant/{tenantId}/entity/{entityId}"],
        consumes = ["application/x-protobuf", "application/json"],
        produces = ["application/x-protobuf", "application/json"])
    fun ruleResultRequest(
        @PathVariable("tenantId") tenantId: String,
        @PathVariable("entityId") entityId: String,
        @RequestBody eRequests: BERequest
    ): ResponseEntity<BRResults> = runBlocking {
        val tags = mapOf("actorKey" to PAKey(entityId, tenantId).toString())
        withLoggingContext(tags) {
            respond(
                // rainbow brackets plugin for IntelliJ is useful here
                logicFunc = {
                    ruleResultConverter.toDto(
                        timers.recordAsync(MetricTypes.RRequestGrpc.id, tags) {
                            async {
                                bEvaluator.execute(evalRequests, entityId, tenantId)
                            }
                        }
                    )
                },
                afterFunc = { logger.info { "Finished all requests in list for $tenantId $entityId" } }
        ) }
    }

    @DeleteMapping("/tenant/{tenantId}/entity/{entityId}")
    fun deleteSession(
        @PathVariable("tenantId") tenantId: String,
        @PathVariable("entityId") entityId: String
    ): ResponseEntity<Boolean> {
        // this block wraps with try/catch with boilerplate logging, and is from the api-common library
        // returns an ResponseEntity.Ok(this return value as body) when it exits the respond block
        return respond(
            logicFunc = { service.deleteSession(PAKey(entityId, tenantId)) },
            afterFunc = { logger.info("Completed REST API to gRPC call for deleteSession for $tenantId $entityId") })
    }

    @GetMapping(value = ["/r"],
            produces = ["application/json"])
    fun getAllRNames(): ResponseEntity<List<String>> = runBlocking {
        val rNames = container.getRNames()
        val tags = mapOf("rNames" to rNames.toString())
        withLoggingContext(tags) {
            respond {
                logger.debug { "Finished returning all the rnames" }
                rNames
            }
        }
    }
}
mockServices:
  - filter:
      includePath:
        - com/ea/p/*
    implementAsObject: true
    generateServiceList: true
    serviceListPackage: com.ea.p.r
    serviceListName: MockRServices

protoBuilders:
  - filter:
      excludePath:
        - google/*
      includePath:
        - com/ea/p/*
    unwrapBuilders: true
    useDslMarkers: true

grpcCoroutines: []

grpcStubExts:
  - supportCoroutines: true

extendableMessages:
  - filter:
      includePath:
        - com/ea/p/r/*
syntax = "proto3";
import "google/protobuf/any.proto";
package com.ea.p.r;
option java_package = "com.ea.p.r.rp";
option java_multiple_files = true;
service RService {
    rpc bREvaluation (BERequest) returns (BRResults);
    rpc deleteSession (DeleteSessionRequest) returns (DeleteSessionResponse);
}

message BERequest {
    string tenantId = 1;
    string entityId = 2;
    repeated ERequestV3 eRequests = 3;
    Service service = 4; 
}

message BRResults {
    repeated EResponseV3 responses = 1;
}

message EResponseV3 {
    string associatedRequestId = 1;
    repeated RResultV3 rResults = 3;
}

message RResultV3 {
    string rId =1;
    bool state = 2;
    map<string, string> results = 3;
}

message ERequestV3 {
    string requestId = 1;
    repeated FDto f = 3;
    repeated string rIdFilter = 4;
}

message Service {
    string service = 1;
    string key = 2;
}

message FactDto {
    string factId = 1;                  
    google.protobuf.Any value = 2;
}

// This is a possible FDto value.
message FJsonValue {      
    string jsonValue = 1;               // The string with the JSON representation
    enum FType {                     // The supported types the JSON representation can be mapped to.
        STRING = 0;
        INTEGER = 1;
        BOOLEAN = 3;
        DOUBLE = 4;
        LIST_INTEGER = 5;
        LIST_STRING = 6;
        STATS_DATA = 7;
    }
    FType fType = 2;
}

message DeleteSessionRequest {
    string tenantId = 1;
    string entityId = 2;
}

message DeleteSessionResponse {
    bool success = 1;
}

Expected:
Works the same as before, starts up fine with a Swagger UI.

Actual:
Fails to start, gets lost in the gRPC generated code.

Workaround:
Remove Kroto Plus, observe that it starts up again.

Integrate PerfMark for grpc trace support

gRPC java is heavily instrumented with PerfMark. It would be useful to integrate specific sections of the coroutines API internals. There might be some difficulties involved since perf mark relies on thread local storage but initial tests produced positive results.

image

Handle exceptions with coroutines.

Now, in development there is not elegant form to show errors in console, we have to put in every coroutine try {} catch and print the error, and in production for example if I have EntityNotFoundException, I have to put a try catch and raise StatusException.

Is there an elegant way to handle exceptions with coroutins? If we use the classic grpc form, we have the interceptors, but in the case of the coroutins, the exception is encapsulated before it can be handled by the server interceptors. I think the errors should be able to be handled like coroutin errors in the context, and if they are not handled there, the server must handle it with interceptors, and if the server don't handle it, the application should be closed(By default should exist a interceptor to catch every exception and handle it, for avoid application closing).

At the moment I have solved it by modifying the library to delegate errors to a Handler from Throw.toRpcException but it is not the best.

Forward documentation for stubs

We can document our RPCs in the proto files like

service GreeterService {
    /* Simply echoes the name prefixed with Hi */
    rpc SayHello (HelloRequest) returns (HelloResponse);
}

The stubs generated by grpc-java contain this documentation as javadocs. However, the coroutine stub generated by kroto plus is missing this documentation.

Android and grpc-protobuf-lite support

Hi, is the client gRPC generator suitable for Android?

I tested per example project and documentation instructions and it's leaking the full grpc-protobuf dependency on the runtime classpath.

+--- com.github.marcoferrer.krotoplus:kroto-plus-coroutines:0.2.2-RC1
|    +--- org.jetbrains.kotlin:kotlin-stdlib:1.3.11
|    |    +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.3.11
|    |    \--- org.jetbrains:annotations:13.0
|    +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.0
|    |    +--- org.jetbrains.kotlin:kotlin-stdlib:1.3.11 (*)
|    |    \--- org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.1.0
|    |         \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.3.11
|    +--- io.grpc:grpc-protobuf:1.15.1
|    |    +--- io.grpc:grpc-core:1.15.1
|    |    |    +--- io.grpc:grpc-context:1.15.1
|    |    |    +--- com.google.code.gson:gson:2.7
|    |    |    +--- com.google.guava:guava:20.0
|    |    |    +--- com.google.errorprone:error_prone_annotations:2.2.0
|    |    |    +--- com.google.code.findbugs:jsr305:3.0.0
|    |    |    +--- org.codehaus.mojo:animal-sniffer-annotations:1.17
|    |    |    +--- io.opencensus:opencensus-api:0.12.3
|    |    |    |    \--- com.google.errorprone:error_prone_annotations:2.2.0
|    |    |    \--- io.opencensus:opencensus-contrib-grpc-metrics:0.12.3
|    |    |         +--- com.google.errorprone:error_prone_annotations:2.2.0
|    |    |         \--- io.opencensus:opencensus-api:0.12.3 (*)
|    |    +--- com.google.protobuf:protobuf-java:3.5.1
|    |    +--- com.google.guava:guava:20.0
|    |    +--- com.google.api.grpc:proto-google-common-protos:1.0.0
|    |    \--- io.grpc:grpc-protobuf-lite:1.15.1
|    |         +--- io.grpc:grpc-core:1.15.1 (*)
|    |         \--- com.google.guava:guava:20.0
|    \--- io.grpc:grpc-stub:1.15.1
|         \--- io.grpc:grpc-core:1.15.1 (*)
\--- junit:junit:4.12
     \--- org.hamcrest:hamcrest-core:1.3

Naming collision with nested fields

I am working on a project that includes the Google pre-built .proto files as explicit files in its proto sources. Kroto-plus works fine in compiling code for most of them (as well as all of our own protos), but comiplation fails when the file .../google/rpc/error_details.proto is included for the protoBuilders generator.

Looking at the generated code, the issue is that objects with the same name as the proto classes are generated which shadow the (imported) classes, so other (generated) code (in the same file) that refers to them by their short names finds the objects instead and breaks.

We've put in a temporary fix of excluding that file, but it would be much more convenient for a proper fix to be available.

Add extensions copy function to messages

After applying this library to our project I found that the only missing builders I have missing are messages that are transformed back to builders to create a copy with a slight modification.

In order to do that I suggest to add a extensions function called copy (the same as data classes) that handles the transformation to builder and the build. It can be written like this:

fun Message.copy(block: Message.Builder.() -> Unit) = this.toBuilder().apply(block).build()

Extending CompilerConfig: InvalidProtocolBufferException "cannot find field"

I try to extend the CompilerConfig with the ProtobufMessages as provided in a PR here (https://github.com/marcoferrer/kroto-plus/pull/13/files).

I migrated all the code, but I can't seem to solve this error when I run generateProto on my sample project app:

Caused by: com.google.protobuf.InvalidProtocolBufferException: 
   Cannot find field: mpProtobufMessages in message krotoplus.compiler.CompilerConfig

I do have this field in my config.proto:

// Configuration entries for the 'Multi-Platform Protobuf Messages' code generator.
repeated MpProtobufMessagesGenOptions mp_protobuf_messages = 27;

Also this field is present in CompilerConfig.java:

public static final int MP_PROTOBUF_MESSAGES_FIELD_NUMBER = 27;
private java.util.List<com.github.marcoferrer.krotoplus.config.MpProtobufMessagesGenOptions> mpProtobufMessages_;

And it is also in the CompilerConfig initialization

  case 218: {
    if (!((mutable_bitField0_ & 0x00000080) == 0x00000080)) {
      mpProtobufMessages_ = new java.util.ArrayList<com.github.marcoferrer.krotoplus.config.MpProtobufMessagesGenOptions>();
      mutable_bitField0_ |= 0x00000080;
    }
    mpProtobufMessages_.add(
        input.readMessage(com.github.marcoferrer.krotoplus.config.MpProtobufMessagesGenOptions.parser(), extensionRegistry));
    break;
  }

In krotoPlusConfig.yaml:

mpProtobufMessages:
  - filter:
      excludePath:
        - google/*

Complete error message from the sample project:

Execution failed for task ':generateProto'.
> protoc: stdout: . stderr: Exception in thread "main" java.lang.reflect.InvocationTargetException
  	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  	at java.lang.reflect.Method.invoke(Method.java:498)
  	at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48)
  	at org.springframework.boot.loader.Launcher.launch(Launcher.java:87)
  	at org.springframework.boot.loader.Launcher.launch(Launcher.java:50)
  	at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51)
  Caused by: com.google.protobuf.InvalidProtocolBufferException: Cannot find field: mpProtobufMessages in message krotoplus.compiler.CompilerConfig
  	at com.google.protobuf.util.JsonFormat$ParserImpl.mergeMessage(JsonFormat.java:1348)
  	at com.google.protobuf.util.JsonFormat$ParserImpl.merge(JsonFormat.java:1308)
  	at com.google.protobuf.util.JsonFormat$ParserImpl.merge(JsonFormat.java:1190)
  	at com.google.protobuf.util.JsonFormat$Parser.merge(JsonFormat.java:370)
  	at com.github.marcoferrer.krotoplus.generators.GeneratorContextKt.getCompilerConfig(GeneratorContext.kt:61)
  	at com.github.marcoferrer.krotoplus.generators.GeneratorContext.<init>(GeneratorContext.kt:36)
  	at com.github.marcoferrer.krotoplus.generators.GeneratorKt$contextInstance$2.invoke(Generator.kt:75)
  	at com.github.marcoferrer.krotoplus.generators.GeneratorKt$contextInstance$2.invoke(Generator.kt)
  	at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
  	at com.github.marcoferrer.krotoplus.generators.GeneratorKt.getContextInstance(Generator.kt)
  	at com.github.marcoferrer.krotoplus.generators.GeneratorKt.initializeContext(Generator.kt:61)
  	at com.github.marcoferrer.krotoplus.generators.GeneratorKt.initializeContext$default(Generator.kt:59)
  	at com.github.marcoferrer.krotoplus.KrotoPlusProtoCMain.main(KrotoPlusProtoCMain.kt:32)
  	... 8 more
  --krotoPlus_out: protoc-gen-krotoPlus: Plugin failed with status code 1.

I am unsure what else I could be doing wrong?

Hopefully someone can help me out, been stuck on this wayyy too long.

Example of server stream subscription

Hello

Can you provide an example of how to subscribe to a server stream please?
In my case, the server sends messages to the stream every second, but when I connect to it like:
Screenshot at Oct 17 17-13-11

I get messages every 1-2 minutes.

Cannot build provided template project

Hi,

The instructions given here: https://github.com/marcoferrer/kroto-plus/tree/master/example-grpc-client-server#quick-start:

git clone https://github.com/marcoferrer/kotlin-coroutines-gRPC-template && \
cd kotlin-coroutines-gRPC-template && \
./gradlew run

produce the following output:

Cloning into 'kotlin-coroutines-gRPC-template'...
remote: Enumerating objects: 57, done.
remote: Counting objects: 100% (57/57), done.
remote: Compressing objects: 100% (37/37), done.
remote: Total 57 (delta 18), reused 44 (delta 9), pack-reused 0
Unpacking objects: 100% (57/57), done.
e: ~/Workspace/kotlin-coroutines-gRPC-template/api/build/generated/source/proto/main/coroutines/io/grpc/examples/helloworld/GreeterCoroutineGrpc.kt: (25, 25): Unresolved reference: Generated
e: ~/Workspace/kotlin-coroutines-gRPC-template/api/build/generated/source/proto/main/coroutines/io/grpc/examples/helloworld/GreeterCoroutineGrpc.kt: (37, 2): Unresolved reference: Generated
> Task :api:compileKotlin FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':api:compileKotlin'.
> Compilation error. See log for more details

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 1s
6 actionable tasks: 6 executed

Instrument gRPC coroutines library with log statements

It would be beneficial to users if they were able to enable log output when debugging issues with coroutines. One of the main reason this was avoided initially was because we didn’t want to tie users to a particular logging framework. Additionally we didn’t want to introduce any overhead into the library.

Using something like googles flogger would provide performant logging and allow users to include whichever backend matches their current logging framework.

Malformed proto sources produces output incompatible with grpc output sources

When dealing with malformed proto sources the protoc-gen-grpc plugin formats service method names using a non standard transformation strategy. Currently kroto plus camel cases these names but it needs to actually emulate the same transformation strategy as the grpc plugin.

Heres an example proto that is valid when processed by the grpc plugin but invalid with krotoplus

service __malformed_SERvice_2__ {

    rpc say_hello (Message1) returns (Message2);

    rpc __sayHEloStr_eaming (stream Message1) returns (stream Message2);

    rpc _____s_a_YH_EloStr_eaming____ (stream Message1) returns (stream Message2);
}

Provide a gradle plugin dsl for easier configuration of the protoc plugin

Using a gradle plugin we can remove to need to provide a configuration file to protoc-gen-kroto-plus. This would make it a lot easier to configure options and allow the configuration to use variables from the build logic.

Since all of the plugins configuration is based on a proto definition, we may be able to just write a generator script to create the needed gradle dsl. This would allow the gradle plugin to automatically stay up to date with changes to configuration options.

Support for `google/protobuf/wrappers.proto`

The Google protobuf standard wrappers are useful with proto3, to handle nullable fields. It would be nice if kroto-plus added explicit support for these wrappers e.g. generate builders for them if they are referenced in the proto files.

Maven Central

Would it be possible to get the latest version of this project pushed up to Maven Central? The README says that it is available on mavenCentral() but the latest version I see there is 0.1.1.

Thanks.

Unresolved references to methods

I have a problem in buildtime with generated code, it seems to be using different naming of method names on a Grpc service than there actually is. I have a proto file like

service BookService {
    // Book detail
    rpc getBook (GetBookRequest) returns (Book) {}
}

and the generated BookServiceGrpc contains method

 public void getBook(com.book.BookOuterClass.GetBookRequest request,
        io.grpc.stub.StreamObserver<com.book.BookOuterClass.Book> responseObserver) {
    ... 
}

but generated file from kroto have this method

suspend fun BookServiceStub.getBook(request: BookOuterClass.GetBookRequest =
        BookOuterClass.GetBookRequest.getDefaultInstance()): BookOuterClass.Book =
        clientCallUnary(request, BookServiceGrpc.getGetBookMethod())

and the problem is in getGetBookMethod. There is no such method and it fails with Unresolved reference: getGetBookMethod
i cant find anything about it in config. Am I doing something wrong? I've tried also another library for kotlin + coroutines (https://github.com/rouzwawi/grpc-kotlin) and the build problem was the same

Note - I am using this in Android project with protobuf-lite, here is my config:

    implementation ("javax.annotation:javax.annotation-api:1.2")
    implementation("com.google.protobuf:protobuf-lite:3.0.1")

    implementation("io.grpc:grpc-core:1.16.1")
    implementation ("io.grpc:grpc-protobuf-lite:1.16.1")
    implementation ("io.grpc:grpc-okhttp:1.16.1")
    implementation ("io.grpc:grpc-stub:1.16.1")

    implementation ("com.github.marcoferrer.krotoplus:kroto-plus-coroutines:0.3.0")
    implementation ("com.github.marcoferrer.krotoplus:kroto-plus-test:0.3.0")
    implementation ("com.github.marcoferrer.krotoplus:kroto-plus-message:0.3.0")
protobuf {
    protoc {
        artifact = 'com.google.protobuf:protoc:3.6.1'
    }
    plugins {
        grpc {
            artifact = 'io.grpc:protoc-gen-grpc-java:1.0.0-pre2'
        }
        javalite {
            artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'
        }
        kroto {
            artifact = "com.github.marcoferrer.krotoplus:protoc-gen-kroto-plus:0.3.0:jvm8@jar"
        }
    }
    generateProtoTasks {
        def krotoConfig = file("krotoconfig.asciipb") // Or .json
        all()*.plugins {
            javalite {}
        }
        ofNonTest()*.plugins {
            grpc {
                // Options added to --grpc_out
                option 'lite'
            }
            kroto {
                outputSubDir = "java"
                option "ConfigPath=$krotoConfig"
            }
        }
    }
}

Add support for malformed protobuf filenames

Not all existing protobuf files follow the convention of snake case for file names. We should ensure that we support any non erroneous format supported by the java and grpc protoc plugins.

Allow server implementations to configure `CoroutineStart` strategy for rpcs

It would be really useful to be able to configure what CoroutineStart strategy is used for invoking rpc methods. The most straight forward approach is to introduce a new field to the ServiceScope interface. One thing to consider for this api is whether or not it should be configurable on a per rpc basis.

The field should default to CoroutineStart.DEFAULT and only allow ATOMIC and UNDISPATCHED as possible values. We're preventing usage of LAZY since it doesnt make sense in this case and introduces issues.

Failed to generate maven coroutines proto for jvm8 classifier in Ubuntu

It is working fine in development (Mac OS). But in Ubuntu environment, I get the below errors. I checked maven repositories all plugin jars are present. Is there any special configuration, I need to make.

kroto-plus.version = 0.5.0

<execution> <id>grpc-coroutines</id> <goals> <goal>compile-custom</goal> </goals> <configuration> <pluginId>kroto-plus</pluginId> <pluginArtifact> com.github.marcoferrer.krotoplus:protoc-gen-kroto-plus:${kroto-plus.version}:jar:jvm8 </pluginArtifact> <pluginParameter>ConfigPath=${project.basedir}/krotoPlusConfig.asciipb</pluginParameter> </configuration> </execution>

Unbuntu Info:

[INFO] ------------------------------------------------------------------------ [INFO] Detecting the operating system and CPU architecture [INFO] ------------------------------------------------------------------------ [INFO] os.detected.name: linux [INFO] os.detected.arch: x86_64 [INFO] os.detected.version: 4.4 [INFO] os.detected.version.major: 4 [INFO] os.detected.version.minor: 4 [INFO] os.detected.release: ubuntu [INFO] os.detected.release.version: 16.04 [INFO] os.detected.release.like.ubuntu: true [INFO] os.detected.release.like.debian: true [INFO] os.detected.classifier: linux-x86_64

Failed logs:
[ERROR] Failed to execute goal org.xolstice.maven.plugins:protobuf-maven-plugin:0.6.1:compile-custom (grpc-coroutines) on project blueprint-proto: Unable to resolve artifact: Missing: [ERROR] ---------- [ERROR] 1) com.github.marcoferrer.krotoplus:protoc-gen-kroto-plus:jar:jvm8:0.5.0 [ERROR] [ERROR] Try downloading the file manually from the project website. [ERROR] [ERROR] Then, install it using the command: [ERROR] mvn install:install-file -DgroupId=com.github.marcoferrer.krotoplus -DartifactId=protoc-gen-kroto-plus -Dversion=0.5.0 -Dclassifier=jvm8 -Dpackaging=jar -Dfile=/path/to/file [ERROR] [ERROR] Alternatively, if you host your own repository you can deploy the file there: [ERROR] mvn deploy:deploy-file -DgroupId=com.github.marcoferrer.krotoplus -DartifactId=protoc-gen-kroto-plus -Dversion=0.5.0 -Dclassifier=jvm8 -Dpackaging=jar -Dfile=/path/to/file -Durl=[url] -DrepositoryId=[id] [ERROR] [ERROR] Path to dependency: [ERROR] 1) org.onap.ccsdk.cds.controllerblueprints:blueprint-proto:jar:0.7.0-SNAPSHOT [ERROR] 2) com.github.marcoferrer.krotoplus:protoc-gen-kroto-plus:jar:jvm8:0.5.0 [ERROR] [ERROR] ---------- [ERROR] 1 required artifact is missing.

Create abstract methods instead of open methods

How come the abstract NameImplBase class generates open functions rather than abstract functions? I think it would be nice to get a compile time error if a method has not been implemented, rather than a runtime error.

gRPC Client / Server Code gen and api

This issue is for tracking any discussions related to #16 PR.

The PR introduces new apis with subtle differences to those introduced in the stub ext generator.
Please reference the client / server example included. It contains a sample of the expected output for discussion.

Here are some key points and examples from the PR

  1. All around better integration with Structured Concurrency
  2. Backpressure Support has been implemented.
  3. Generation of Client Stubs which implement the CoroutineScope interface
    • Allows client stubs to work well with Structured Concurrency
    • Cancellations can now be propagated across usages of a specific stub instance.
  4. Generation of abstract Service Base Impl's
    • Allows services to still support common grpc-java patterns, while still fully embracing coroutine idioms and features.
  5. Convenience exts for sending requests and responses in both client, server code.
    • Full support for familiar Kroto-plus convenience lambda exts on SendChannel.send { } and CompletableDeferred.complete { }
  6. Support for annotation processor assistance via RpcMethod annotation from grpc-java. RpcMethod.java
suspend fun performUnaryCall(stub: GreeterCoroutineGrpc.GreeterCoroutineStub){

    val unaryResponse = stub.sayHello { name = "John" }

    println("Unary Response: ${unaryResponse.message}")
}

suspend fun performServerStreamingCall(stub: GreeterCoroutineGrpc.GreeterCoroutineStub){

    val responseChannel = stub.sayHelloServerStreaming { name = "John" }

    responseChannel.consumeEach {
        println("Server Streaming Response: ${it.message}")
    }
}

suspend fun CoroutineScope.performClientStreamingCall(stub: GreeterCoroutineGrpc.GreeterCoroutineStub){

    // Client Streaming RPC
    val (requestChannel, response) = stub.sayHelloClientStreaming()

    launch {
        repeat(5){
            requestChannel.send { name = "person #$it" }
        }
        requestChannel.close()
    }

    println("Client Streaming Response: ${response.await().toString().trim()}")
}

suspend fun CoroutineScope.performBidiCall(stub: GreeterCoroutineGrpc.GreeterCoroutineStub){

    val (requestChannel, responseChannel) = stub.sayHelloStreaming()

    launch {
        repeat(5){
            requestChannel.send { name = "person #$it" }
        }
        requestChannel.close()
    }

    launch {
        responseChannel.consumeEach {
            println("Bidi Response: ${it.message}")
        }
    }
}

Runtime exception when trying to run the kotlin-coroutines-gRPC template

I'm running kotlin version 1.3.31-release-197 on JVM 12

Task :run FAILED Unary Response: Hello there, John! Bidi Response: Hello there, person #0! Bidi Response: Hello there, person #1! Bidi Response: Hello there, person #2! Bidi Response: Hello there, person #3! Bidi Response: Hello there, person #4! Exception in thread "main" io.grpc.StatusRuntimeException: UNKNOWN: Receiver class com.github.marcoferrer.krotoplus.coroutines.server.ServerRequestStreamChannel does not define or inherit an implementation of the resolved method abstract cancel(Ljava/util/concurrent/CancellationException;)V of interface kotlinx.coroutines.channels.ReceiveChannel.

sourceSet other than `main` won't output

When I run gradle, it generates output files for the proto-files. These are put into $projectDir/src/$sourceSet/java/my/org/models/a.kt (or a.java). This works for the sourceSet main, but not for other sourceSets like commonMain, although I defined the task for all sourceSets. The protofiles and directories are identical between sourceSet main and commonMain (I created main just for testing).

Partial gradle build file here (https://gist.github.com/RdeWilde/ec3a5c9432004579643a47ed5fee3397)

Directory structure is:

src
+ commonMain
|    |
|    + java     // no output  :-(
|    + proto
|         |
|         + protofile1.proto
|         + protofile2.proto
|
+ main
     |
     + java    // does have output  :-)
     + proto
          |
          + protofile1.proto
          + protofile2.proto

krotoplus.script.cache.dir is not respected

We have a case when KrotoPlus projects needs to run in a sandboxed environment.

In this case, it tries to create scripts cache, but fails, since sandbox doesn't allow it to generate a file at HOME level.

In theory, there's krotoplus.script.cache.dir property for that:

private val cacheDirPath = System.getProperty("krotoplus.script.cache.dir") ?: ("${System.getProperty("user.home")

But it doesn't seem to work in any way:

-Dkrotoplus.script.cache.dir=./some_dir

Will reflect correctly when used as System.getProperty("krotoplus.script.cache.dir") in the main script, but not by ScriptManager

My theory is there's some kind of forking happening while generating scripts, and the original property is lost. This is supported by the following stack trace:

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':generateProto'.
> protoc: stdout: . stderr: <screened> : warning: directory does not exist.
  Exception in thread "main" java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48)
        at org.springframework.boot.loader.Launcher.launch(Launcher.java:87)
        at org.springframework.boot.loader.Launcher.launch(Launcher.java:50)
        at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51)
  Caused by: java.io.IOException: No such file or directory
        at java.io.UnixFileSystem.createFileExclusively(Native Method)
        at java.io.File.createNewFile(File.java:1012)
        at com.github.marcoferrer.krotoplus.script.ScriptManager.compileAndWriteToFs(ScriptManager.kt:125)
        at com.github.marcoferrer.krotoplus.script.ScriptManager.getOrLoadCompiledClasses(ScriptManager.kt:118)
        at com.github.marcoferrer.krotoplus.script.ScriptManager.getScript$protoc_gen_kroto_plus(ScriptManager.kt:90)
        at com.github.marcoferrer.krotoplus.script.ScriptManager.getScript$protoc_gen_kroto_plus(ScriptManager.kt:96)
        at com.github.marcoferrer.krotoplus.generators.GeneratorScriptsGenerator.invoke(GeneratorScriptsGenerator.kt:32)
        at com.github.marcoferrer.krotoplus.generators.GeneratorScriptsGenerator.invoke(GeneratorScriptsGenerator.kt:22)
        at com.github.marcoferrer.krotoplus.KrotoPlusProtoCMain$main$deferredGenResponse$1$2$1.invokeSuspend(KrotoPlusProtoCMain.kt:48)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:238)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)
  --kroto_out: protoc-gen-kroto: Plugin failed with status code 1.

Suggestion would be to use System.getenv instead of System.getProperty(), as ENV would propagate from OS.

Maven

Is there a plan to support maven?

Client cancellations aren't propagated to the server

When I close a streaming client request with an exception I would expect the corresponding server receive channel to throw an exception upon calling receive (or iterating the channel) but it doesn't seem to do so - am I doing something wrong?

Client:

call.requestChannel.close(Exception("Testing Exception"))

Server:

try {
    for (request in requestChannel) {
        logger.info("Iterating")
    }
    logger.info("Closed normally")
} catch (e: Exception){
    logger.error("Caught exception", e)
}

Dokka Configuration

It would be nice to see an example dokka configuration that would display the auto-generated code; preferably with DSL examples.

square wire difference

I see, that there is Wire from Square which is also dealing with gRPC and utilise Kotlin as well.

I see, that it is mentioned few times in issues like this one #89 (comment), but I don't see how this project is comparable / distinguishable from it?

Perhaps it could be nice for new people to understand this before opting-in for one or another.

Add additional utility overloads for repeated fields in a proto class

I've been using a lot the generated builders with nested objects and that creates really nice to read structures but I think we can improve them even more adding a couple of overloads to the methods that handle repeated fields.

For example given a proto message like:

message Item {
uint32 id = 1;
string value = 2;
}

message ItemList {
repeated Item item = 1;
}

by default we have available addAllItem(items: Iterable<Item>) and addItem(item: Item).

What I suggest to add if the following overloads.
addAllItem(vararg items: Item) --> this one would avoid to call listOf to add items that are being defined manually
addItem(block: Item.Builder.() -> Unit) --> this one would be to avoid the redundant information that you get while reading addItem(Item { id = 1, value = "test"})

Let me know if you think this could be added to the base of Kroto+ or if we should make it into a custom script.

Generating coroutines for existing Java stubs (without .proto)?

May I admit ignorance of Kroto+ internals. Does Kroto+ require .proto file(s), or does it [after protoc and javac] load the Java classes, inspects them by reflection, and generates the coroutines from them only?

Say you have access to Java classes generated by protoc (for example, as artifacts from Maven). Removing a copy of .proto would mean one less step to maintain. (Of course, if the generated classes change, then the developer may have to update the coroutines, too.)

protobuf-maven-plugin doesn't support protoc insertions

I use maven and faced with a problem when had used your plugin.
Short log of problem is
[ERROR] PROTOC FAILED: some/path//SomeClass.java: Tried to insert into file that doesn't exist.
My pom.xml

<plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:3.6.1:exe:${os.detected.classifier}</protocArtifact>
                    <clearOutputDirectory>false</clearOutputDirectory>
                    <outputDirectory>${basedir}/target/generated-sources/protobuf/java/</outputDirectory>
                </configuration>
                <executions>
                    <execution>
                        <goals><goal>compile</goal></goals>
                    </execution>
                    <execution>
                        <id>grpc-java</id>
                        <goals><goal>compile-custom</goal></goals>
                        <configuration>
                            <pluginId>grpc-java</pluginId>
                            <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.18.0:exe:${os.detected.classifier}</pluginArtifact>
                        </configuration>
                    </execution>
                    <execution>
                        <id>kroto-plus</id>
                        <goals>
                            <goal>compile-custom</goal>
                        </goals>
                        <configuration>
                            <pluginId>kroto-plus</pluginId>
                            <pluginArtifact>com.github.marcoferrer.krotoplus:protoc-gen-kroto-plus:${krotoplus.version}:jar:jvm8</pluginArtifact>
                            <pluginParameter>ConfigPath=${project.basedir}/krotoPlusConfig.asciipb</pluginParameter>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

as noticed there, there and there

Using dsl markers relies on protoc insertions, so take care to ensure that the kroto-plus output directory is the same as the directory for generated java code

Then I tried protobuf-gradle-plugin with settings

protobuf {
    protoc { artifact = "com.google.protobuf:protoc:${versions.protobuf}" }
    generatedFilesBaseDir = "$buildDir/generated-sources"
    plugins {
        grpc { artifact = "io.grpc:protoc-gen-grpc-java:${versions.grpc}" }
        kroto {
            artifact = "com.github.marcoferrer.krotoplus:protoc-gen-kroto-plus:${versions.krotoplus}:jvm8@jar"
        }
    }
    generateProtoTasks {
        def krotoConfig = file("krotoPlusConfig.asciipb")
        all().each { task ->
            task.inputs.files krotoConfig
            task.plugins {
                grpc { outputSubDir = "java" }
                kroto {
                    outputSubDir = "java"
                    option "ConfigPath=$krotoConfig"
                }
            }
        }
    }
}

and that worked.
I ran command in debug mode and compare output protobuf-maven-plugin and protobuf-gradle-plugin.
protobuf-gradle-plugin run only one protoc command with pipe of plugins

protoc-3.6.1-linux-x86_64.exe -I/api/src/main/proto -I/api/build/extracted-protos/main -I/api/build/extracted-include-protos/main --java_out=/api/build/generated-sources/main/java --plugin=protoc-gen-grpc=/home/stepan/.gradle/caches/modules-2/files-2.1/io.grpc/protoc-gen-grpc-java/1.16.1/f26e45fd6dc4ebaf6d76b22b290b337ee96aed54/protoc-gen-grpc-java-1.16.1-linux-x86_64.exe --grpc_out=/api/build/generated-sources/main/java --plugin=protoc-gen-kroto=/home/stepan/.gradle/caches/modules-2/files-2.1/com.github.marcoferrer.krotoplus/protoc-gen-kroto-plus/0.2.2-SNAPSHOT/9c02fa688b3c00b1f768fb9302e85205a798fc05/protoc-gen-kroto-plus-0.2.2-SNAPSHOT-jvm8.jar --kroto_out=ConfigPath=/api/krotoPlusConfig.asciipb:/api/build/generated-sources/main/java some_protos.proto ...

--java_out --> --protoc-gen-grpc --> protoc-gen-kroto
protobuf-maven-plugin run protoc commands N executions times
and in my case last command is

protoc-3.6.1-linux-x86_64.exe --proto_path=/api/src/main/proto --proto_path=/api/target/protoc-dependencies/3f38729cce82adb9f25c7327ca2059db --proto_path=/api/target/protoc-dependencies/5f1e9201db2bd80331b59aeb26f3d508 --plugin=protoc-gen-kroto-plus=/api/target/protoc-plugins/protoc-gen-kroto-plus-0.2.2-SNAPSHOT-jvm8.jar --kroto-plus_out=ConfigPath=/api/krotoPlusConfig.asciipb:/api/target/generated-sources/protobuf/java some_protos.proto ...

that lead to build failure.
When I have added --java_out=/api/target/generated-sources/protobuf/java to command above. It worked fine

protoc-3.6.1-linux-x86_64.exe --proto_path=/api/src/main/proto --proto_path=/api/target/protoc-dependencies/3f38729cce82adb9f25c7327ca2059db --proto_path=/api/target/protoc-dependencies/5f1e9201db2bd80331b59aeb26f3d508 --java_out=/api/target/generated-sources/protobuf/java --plugin=protoc-gen-kroto-plus=/api/target/protoc-plugins/protoc-gen-kroto-plus-0.2.2-SNAPSHOT-jvm8.jar --kroto-plus_out=ConfigPath=/api/krotoPlusConfig.asciipb:/api/target/generated-sources/protobuf/java some_protos.proto ...

I don't know which products this bug\behavior belongs to.

Generate component extensions for destructuring support.

Several cases have come up where users have wanted to be able to destructure message with the familiar kotlin syntax. Although this is highly requested it doesn’t come for free. It can become massively error prone for users if they re arrange fields ids or introduce new ones.

I still think a safe implementation can be provided by allowing users to define a “deconstructor” option within their message. Similar to the common proto support of the method signature option. The trade off is that this would make the component extensions explicitly defined and managed by the proto author instead of implicitly determined by the plugin.

There’s a already a branch in progress with a working example but the majority of the effort needed for this is in writing good documentation.

Issues running on Windows

Ok, I don't use Windows myself (ugh), but some of my coworkers do. There are issues running this on Windows:

  1. The compiler is run with an invalid path that contains a leading "/". Here is the debug output from Gradle:
11:48:22.846 [INFO] [org.gradle.process.internal.DefaultExecHandle] Starting process 'command 'C:\jdk1.8.0_191\bin\java.exe''. Working directory: H:\source\myproject\lib-proto Command: C:\jdk1.8.0_191\bin\java.exe -Dfile.encoding=windows-1252 -Duser.country=US -Duser.language=en -Duser.variant -jar /C:/Users/Me/.gradle/caches/modules-2/files-2.1/com.github.marcoferrer.krotoplus/kroto-plus-compiler/0.1.3/620b2a278b4d4beed80320bb4800339967f850d7/kroto-plus-compiler-0.1.3.jar H:\source\myproject\lib-proto/src/main/proto H:\source\myproject\lib-proto\build/extracted-include-protos/main -default-out H:\source\myproject\lib-proto\build\generated\source\proto\main\kotlin -writers 3 -StubOverloads -o|H:\source\myproject\lib-proto\build\generated\source\proto\main\kotlin|-coroutines -MockServices -o|H:\source\myproject\lib-proto\build\generated\source\proto\main\kotlin -ProtoTypeBuilder
...
11:48:22.874 [ERROR] [system.err] Error: Unable to access jarfile /C:/Users/Me/.gradle/caches/modules-2/files-2.1/com.github.marcoferrer.krotoplus/kroto-plus-compiler/0.1.3/620b2a278b4d4beed80320bb4800339967f850d7/kroto-plus-compiler-0.1.3.jar

Note the leading slash on the -jar argument.

  1. I don't know if this is an issue in this plugin or in the upstream protobuf gradle plugin, but when adding the kroto code gen plugin to the protobuf plugin, the build fails with the error:
Execution failed for task ':generateProto'.
> protoc: stdout: . stderr: --kroto_out: protoc-gen-kroto: %1 is not a valid Win32 application.

Looking at the --debug logs, it looks like a --plugin parameter is addd to the call to protoc, with the following value:

 --plugin=protoc-gen-kroto=C:\Users\Me\.gradle\caches\modules-2\files-2.1\com.github.marcoferrer.krotoplus\protoc-gen-kroto-plus\0.1.3\3c41d071d04c8558822447643894490e33a857b7\protoc-gen-kroto-plus-0.1.3-jvm8.jar

but protoc is assuming the jar file is an executable it can run. I have tried associating ".jar" files with "java" (and this seems to have worked), but for some reason when running via protoc, the same "not a valid Win32 application" error still happens.

Uncaught exceptions

Hey there,

So I have a very simple service:

@GrpcService
class SimpleThrowingService() : SimpleThrowingServiceCoroutineGrpc.SimpleThrowingServiceImplBase() {

    private val exceptionHandler = CoroutineExceptionHandler { _, ex ->
        logger.error("Exception caught", ex)
    }

    override val initialContext: CoroutineContext
        get() = exceptionHandler

    override suspend fun throwingStream(request: Empty, responseChannel: SendChannel<Int>) {
        throw IllegalArgumentException(":(")
    }
}

Because I'm setting exceptionHandlerContext into the initialContext I would expect the handler catch the exception and log the message. Unfortunately, what happens is... Nothing. The exception gets swallowed and not even printed out.

When I however start a new CoroutineScope with my exceptionHandler as it's context, that seems to work fine.

Currently I'm on 22-RC3

Why does that happen?

Thanks!

Why there isn't Flow for streams?

Hello, great library! I have a question if using of Flow instead of Channels is considered in the future. As far as I understand its more suitable since it supports cold streams. Also lot of operators on Channels are deprecated in favor of Flow.

compile-custom fails on Windows 10

It works on Ubuntu, MacOS but fails on Windows 10.
Using below execution configutration (with kroto-plus.version = 0.5.0)

<execution>
    <id>grpc-coroutines</id>
    <goals>
        <goal>compile-custom</goal>
    </goals>
    <configuration>
        <pluginId>kroto-plus</pluginId>
        <pluginArtifact>com.github.marcoferrer.krotoplus:protoc-gen-kroto-plus:${kroto-plus.version}:jar:jvm8</pluginArtifact>
        <pluginParameter>ConfigPath=./krotoPlusConfig.asciipb</pluginParameter>
    </configuration>
</execution>

Windows Info:

[INFO] ------------------------------------------------------------------------
[INFO] Detecting the operating system and CPU architecture
[INFO] ------------------------------------------------------------------------
[INFO] os.detected.name: windows
[INFO] os.detected.arch: x86_64
[INFO] os.detected.version: 10.0
[INFO] os.detected.version.major: 10
[INFO] os.detected.version.minor: 0
[INFO] os.detected.classifier: windows-x86_64

Failed Error Logs:

[INFO] --- protobuf-maven-plugin:0.6.1:compile-custom (grpc-coroutines) @ blueprint-proto ---
[INFO] Compiling 4 proto file(s) to C:\git\modules\target\generated-sources\protobuf\kroto-plus
[ERROR] PROTOC FAILED: --kroto-plus_out: protoc-gen-kroto-plus: This version of %1 is not compatible with the version of Windows you're running. Check your computer's system information and then contact the software publisher.

[ERROR] C:\git\modules\components\proto-definition\proto\CommandExecutor.proto [0:0]: --kroto-plus_out: protoc-gen-kroto-plus: This version of %1 is not compatible with the version of Windows you're running. Check your computer's system information and then contact the software publisher.

[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------

Refactor server calls to implement ServerCallHandler

Currently server implementations use server handler builder to process incoming calls using the existing stream observer api. This requires a lot of adapter code just to map coroutine behavior to the existing stream observer call back.

It should be possible to implement ServerCallHandler and emit its usage in the generated code. This gives us the ability to not only improve performance but map the coroutine apis closer to the call level.

At the moment it is difficult to expand the kroto API. With access to the call, we could potentially allow users to pass execution parameters via a call wrapper type. This would allow the customization of the following:

  • Server method CoroutineStart
  • Initial coroutineContext
  • Channel buffer size for streaming APIs
  • (Possibly?) Custom Exception handlers

Call wrapper example:

abstract class ServerCallCoroutine<ReqT,RespT>(
    delegate: ServerCall<ReqT,RespT>
) : SimpleForwardingServerCall<ReqT,RespT>(delegate){

    open val initialContext: CoroutineContext = EmptyCoroutineContext

    open val coroutineStart: CoroutineStart = CoroutineStart.ATOMIC
    
    open val requestChannelBufferSize: Int = Channel.BUFFER

}

The kroto implementation of ServerCallHandler would be able to check for this type explicitly during method invocation and use the parameters provided

Add common interface to outter 'Grpc' object for extension support.

Consider providing a common interface for the outter 'Grpc' object which encloses the generated
stub and service base impl

Reasoning behind this is to allow users to write extensions for building stubs with common configurations specific to their application.

A couple have cases have come up where this would be useful but pending feedback will really drive the inclusion of this feature.

fun <W : GrpcWrapper<Stub>, Stub> W.newStubWithMyDefaultsAndParent(channel: Channel, parent: Job): Stub =
	newStub(channel)
		.withCoroutineContext(parent + Dispatchers.Default)
		.withWaitForReady()
		.withDeadline(...)
        	.withInterceptors(...)

// Usage: GreeterCoroutineGrpc.newStubWithMyDefaultsAndParent(channel, job)


fun <W : GrpcWrapper<Stub>, Stub> Channel.newStub(wrapper: W): Stub =
	wrapper.newStub(channel)

// Usage: channel.newStub(GreeterCoroutineGrpc)

Kotlinx Serialization Multiplatform Protobuf Generator

This is the tracking issue for #13
Originally work on this feature was blocked due to a bug with the @SerialInfo annotation in the kotlin serialization native runtime. It looks like the bug is addressed in versions 0.10.0-eap-1 and 1.3.20-eap-52 with an expected release in 1.3.20

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.