-
Notifications
You must be signed in to change notification settings - Fork 101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Server support #447
Add Server support #447
Conversation
Codecov Report
@@ Coverage Diff @@
## master #447 +/- ##
============================================
+ Coverage 59.53% 60.42% +0.89%
- Complexity 796 847 +51
============================================
Files 60 61 +1
Lines 5535 6198 +663
Branches 876 981 +105
============================================
+ Hits 3295 3745 +450
- Misses 1996 2153 +157
- Partials 244 300 +56
Continue to review full report at Codecov.
|
One other thing that is a bit suboptimal is that the default error handler does not log anything when an error occurs. I didn't add that because up until now there was no dependency on any logging framework in |
Side note: I am pretty confident that apart from the default requiredness thing (which is very minor), Thrifty is 1 to 1 compatible/equivalent with the thrift implementation. |
Friendly nudge ;) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey, I'm very sorry for the delay. This honestly just fell off my radar after a very hectic month at work.
This is a great piece of work! I quibble about some minor things, but not enough to block on. I'd like to get this landed and refine as needed.
|
||
fun generate(schema: Schema): List<FileSpec> { | ||
val specsByNamespace = LinkedHashMultimap.create<String, TypeSpec>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we promote these to instance variables, let's clear them at the start of generate
. If nothing else, it may make test failures a bit less confusing if we were to ever re-use an instance of KotlinCodeGenerator.
import okio.Buffer | ||
|
||
|
||
class TestServer @JvmOverloads constructor(private val protocol: ServerProtocol = ServerProtocol.BINARY) : Extension, BeforeEachCallback, AfterEachCallback { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious at the use of @JvmOverloads
. Do you expect this to be used from Java?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file was based on the existing TestServer.java
that I converted to Kotlin using IntelliJ, so it was so helpful as to automatically add the annotation :)
I've removed it now.
* 2 => { 6 => <empty Insanity struct>, }, | ||
* } | ||
*/ | ||
val result: MutableMap<Long, Map<com.microsoft.thrifty.integration.kgen.coro.Numberz, com.microsoft.thrifty.integration.kgen.coro.Insanity>> = LinkedHashMap() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If possible can we import these types?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep!
@@ -62,6 +62,7 @@ import com.microsoft.thrifty.service.AsyncClientBase | |||
import com.microsoft.thrifty.service.MethodCall | |||
import com.microsoft.thrifty.service.ServiceMethodCallback | |||
import com.microsoft.thrifty.service.TMessageType | |||
import com.microsoft.thrifty.service.server.* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: we generally try to avoid wildcard imports.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
@@ -311,6 +317,21 @@ class KotlinCodeGenerator( | |||
} | |||
} | |||
|
|||
if (generateServer) { | |||
check(coroutineServiceClients) { "Server feature currently requires coroutineServiceClients to be enabled" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you considered having the generate do either clients or servers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, good point. I wanted to add as little stuff as possible and thus thought it would be a good idea to reuse the client interface as the service interface (server side), because the signatures are exactly going to match.
But because it did not make much sense to me to implement the callback based variant for the server side, I chose to force the coroutine client flag so that the interface would be generated with coroutine suspend keywords and that's what the server generator will generated against.
It is kinda hacky though and you are totally right, I would suspect you either generate the client or the server, but rarely both at the same time.
For this reason and also because of the weird interaction between client and server implementations, it would make more sense to generate a separate interface just for the server and then have that either blocking or suspending controlled via its own flag (in case you want to build a non-blocking thrift server e.g. using ktor), but not generate a callback-based implementation.
That means I need to copy the method that generates the client interface and adjust it for server generation. Also not great, but better than the current state I guess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be fixed now.
|
||
spec.addCode { | ||
beginControlFlow("else -> ") | ||
addStatement("""throw %T("%L")""", IllegalArgumentException::class, "Unknown method \${msg.name}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Be careful with using Foo::class
as type arguments. Kotlinpoet has an iffy track record with understanding, for example kotlin.Exception
- historically it will often end up emitting references to the underlying JVM type, which is ok I guess for this use-case but forecloses on MPP servers too.
tl;dr let's add an entry to the ClassNames
object up around line 110 of this file and use that instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see a few other places in the PR (and probably in my code, too, let's be honest) where ::class
literals are being used this way. So long as they aren't typealiases or expect/actual types, it's probably OK, but we should make sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly I have almost no experience with Kotlin Multiplatform, so I tried to do what you said to the places I found :)
thrifty-runtime/src/commonMain/kotlin/com/microsoft/thrifty/ThriftException.kt
Show resolved
Hide resolved
Sorry for the long delay, was struggling to find the motivation to sit down after work to get this done :) |
Codecov Report
@@ Coverage Diff @@
## master #447 +/- ##
============================================
+ Coverage 57.27% 58.38% +1.11%
- Complexity 789 841 +52
============================================
Files 67 68 +1
Lines 5746 6421 +675
Branches 854 957 +103
============================================
+ Hits 3291 3749 +458
- Misses 2233 2397 +164
- Partials 222 275 +53
Continue to review full report at Codecov.
|
Server generation now always generates its own service interface in a |
I have addressed your feedback (thanks a lot!) and added the missing license headers. |
Nice, thank you! This generally looks good. I'm going to hold off on merging this, however, until after we finalize a 3.0.0 release. That should be fairly soon, but I'm still working out some problems with it. |
A whole month later, I've finally released 3.0.0. Thanks for your extraordinary patience. Let's get this landed! |
and make kind required (there is an "unknown" type anyways)
in case the generator gets re-used
in order to try to be as compatible with Kotlin Multiplatform as possible.
in a server-specific namespace instead of reusing the client interface. This is necessary because we always want to generate a coroutine based interface for the server and never a callback based one.
because now we generate a separate FileSpec for the server that also need the types from the "normal" FileSpec.
as it no longer requires the coroutine client flag to be set.
I have pulled your changes in via a rebase fixing any conflicts and only after I was done did I realize that that's not the most transparent way for you to track what has changed. Was totally in my work-workflow, sorry. I guess you would have probably preferred a merge commit? |
README.md
Outdated
`--experimental-kt-generate-server` enabled code generation for the server portion of a thrift service. You can | ||
use this to implement a thrift server with the same benefits as the kotlin client: no runtime surprises thanks to | ||
structs being always valid by having nullability guarantees and unions represented as sealed classes. | ||
See | ||
|
||
### Thanks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This readme block looks misplaced. Is this an artifact of the rebase?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, seems I somehow messed that up. Fixed now.
Bump :) |
oh geez, were you waiting for me? I thought I was waiting for you 😧 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excited to have this in a shareable state and very interested in hearing your feedback :)
I created separate commits for refactorings that were required for the new code that then gets added "append-only" in one larger commit to make it easier to gauge the impact (rather non-impact) on the existing code.
One thing that is not perfect yet is that the synthetic arg and result structs are generated on the same level as the normal ones, opening the possibility for a name clash. Ideally, I would want them nested inside the
ServerCall
class, but that required changes to the existing data class/union generators, so I skipped it for now.