Skip to content
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

Fix Gen Class Without Any Properties #15

Merged
merged 4 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.nio.file.Path
import kotlin.io.path.Path


/**
* Links are used to keep track of the [TypeName] of the fields and the types they reference before the code
* is generated. This way, code generation of types do not depend on each other and can be generated in any
Expand Down Expand Up @@ -164,6 +165,45 @@
return fileSpec
}

/**
* Check if the given message descriptor contains any fields or not.
*
* This is used to avoid generating data classes with 0 properties because they are not allowed in Kotlin.
*
* @param messageDescriptor the descriptor to check if it has any fields.
* @return whether the given message descriptor contains any fields or not.
*/
fun hasNoField(messageDescriptor: Descriptors.Descriptor): Boolean {
return messageDescriptor.fields.isEmpty()
}

/**
* Get the overriden methods for a method with no fields.
*
* @param messageDescriptor descriptor of the message to generate empty override methods for.
* @return a list of [FunSpec]s that contain the overriden `toString`, `hashCode` and `equals`.
*/
private fun getEmptyOverrideFunSpecs(messageDescriptor: Descriptors.Descriptor): List<FunSpec> {
return listOf(
FunSpec.builder("toString")
.addModifiers(KModifier.OVERRIDE)
.addStatement("return %S", messageDescriptor.name)
.returns(String::class)
.build(),
FunSpec.builder("hashCode")
.addModifiers(KModifier.OVERRIDE)
.addStatement("return %L", messageDescriptor.name.hashCode())
.returns(Int::class)
.build(),
FunSpec.builder("equals")
.addModifiers(KModifier.OVERRIDE)
.addParameter("other", Any::class.asTypeName().copy(nullable = true))
.addStatement("return other is ${messageDescriptor.name}")
.returns(Boolean::class)
.build()

Check warning on line 203 in app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt#L187-L203

Added lines #L187 - L203 were not covered by tests
)
}

/**
* Generate a single parameter for the given [Descriptors.FieldDescriptor]. Returns a
* [ParameterSpec.Builder] so users can add additional code to the parameter.
Expand Down Expand Up @@ -195,9 +235,14 @@
*/
private fun generateSingleClass(messageDescriptor: Descriptors.Descriptor): TypeSpec.Builder {
val typeSpec = TypeSpec.classBuilder(messageDescriptor.name)
.addModifiers(KModifier.DATA)
.addAnnotation(Serializable::class)

if (hasNoField(messageDescriptor)) {
typeSpec.addFunctions(getEmptyOverrideFunSpecs(messageDescriptor))

Check warning on line 241 in app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt#L241

Added line #L241 was not covered by tests
} else {
typeSpec.addModifiers(KModifier.DATA)

Check warning on line 243 in app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/dogacel/kotlinx/protobuf/gen/CodeGenerator.kt#L243

Added line #L243 was not covered by tests
}

// A Data class needs a primary constructor with all the parameters.
val parameters = messageDescriptor.fields.map { generateSingleParameter(it).build() }
val constructorSpec = FunSpec
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package dogacel.kotlinx.protobuf.gen

import messages.MessageNoFieldsOuterClass.MessageNoFields
import kotlin.test.Test
import kotlin.test.assertEquals

class CodeGeneratorTest {

@Test
fun hasAnyFieldShouldWork() {
val codeGenerator = CodeGenerator()

val messageNoFields = MessageNoFields.getDescriptor()
val messageSomeFields1 = MessageNoFields.SubMessageNoFields.getDescriptor()
val messageSomeFields2 = MessageNoFields.SubMessageNoFields.SubMessageNoFieldsExtend.getDescriptor()
val messageSomeFieldsOneof = MessageNoFields.SubMessageOneofFields.getDescriptor()

assertEquals(true, codeGenerator.hasNoField(messageNoFields))
assertEquals(false, codeGenerator.hasNoField(messageSomeFields1))
assertEquals(false, codeGenerator.hasNoField(messageSomeFields2))
assertEquals(false, codeGenerator.hasNoField(messageSomeFieldsOneof))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package testgen.messages

import kotlin.Any
import kotlin.Boolean
import kotlin.Int
import kotlin.String
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber

@Serializable
public class MessageNoFields() {
override fun toString(): String = "MessageNoFields"

Check warning on line 12 in generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt

View check run for this annotation

Codecov / codecov/patch

generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt#L12

Added line #L12 was not covered by tests

override fun hashCode(): Int = 70_682_849

Check warning on line 14 in generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt

View check run for this annotation

Codecov / codecov/patch

generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt#L14

Added line #L14 was not covered by tests

override fun equals(other: Any?): Boolean = other is MessageNoFields

Check warning on line 16 in generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt

View check run for this annotation

Codecov / codecov/patch

generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt#L16

Added line #L16 was not covered by tests

@Serializable

Check warning on line 18 in generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt

View check run for this annotation

Codecov / codecov/patch

generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt#L18

Added line #L18 was not covered by tests
public data class SubMessageNoFields(
@ProtoNumber(number = 1)

Check warning on line 20 in generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt

View check run for this annotation

Codecov / codecov/patch

generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt#L20

Added line #L20 was not covered by tests
public val subHello: SubMessageNoFields? = null,
) {
@Serializable
public data class SubMessageNoFieldsExtend(
@ProtoNumber(number = 1)

Check warning on line 25 in generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt

View check run for this annotation

Codecov / codecov/patch

generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt#L25

Added line #L25 was not covered by tests
public val type: Type = testgen.messages.MessageNoFields.Type.UNKNOWN,
)
}

@Serializable

Check warning on line 30 in generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt

View check run for this annotation

Codecov / codecov/patch

generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt#L30

Added line #L30 was not covered by tests
public data class SubMessageOneofFields(
@ProtoNumber(number = 1)

Check warning on line 32 in generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt

View check run for this annotation

Codecov / codecov/patch

generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt#L32

Added line #L32 was not covered by tests
public val someValue: Int? = null,
) {
init {

Check warning on line 35 in generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt

View check run for this annotation

Codecov / codecov/patch

generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt#L35

Added line #L35 was not covered by tests
require(
listOfNotNull(
someValue,
).size <= 1
) { "Should only contain one of some_oneof." }
}

Check warning on line 41 in generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt

View check run for this annotation

Codecov / codecov/patch

generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt#L41

Added line #L41 was not covered by tests
}

@Serializable

Check warning on line 44 in generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt

View check run for this annotation

Codecov / codecov/patch

generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt#L44

Added line #L44 was not covered by tests
public enum class Type {
@ProtoNumber(number = 0)

Check warning on line 46 in generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt

View check run for this annotation

Codecov / codecov/patch

generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt#L46

Added line #L46 was not covered by tests
UNKNOWN,
@ProtoNumber(number = 1)

Check warning on line 48 in generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt

View check run for this annotation

Codecov / codecov/patch

generated-code-tests/src/main/kotlin/testgen/messages/message_no_fields.proto.kt#L48

Added line #L48 was not covered by tests
KNOWN,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package dogacel.kotlinx.protobuf.gen.proto
import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.encodeToByteArray
import kotlinx.serialization.protobuf.ProtoBuf
import testgen.messages.MessageNoFields
import testgen.messages.MessagesMessage
import kotlin.test.Test
import kotlin.test.assertEquals
Expand Down Expand Up @@ -36,7 +37,10 @@ class MessageTest {
assertEquals(message.id, result.id)
assertEquals(message.optionalForeignMessage.c, result.optionalForeignMessage?.c)
assertEquals(message.optionalNestedMessage.a, result.optionalNestedMessage?.a)
assertEquals(message.optionalNestedMessage.corecursive.id, result.optionalNestedMessage?.corecursive?.id)
assertEquals(
message.optionalNestedMessage.corecursive.id,
result.optionalNestedMessage?.corecursive?.id
)
assertEquals(
message.optionalNestedMessage.corecursive.optionalForeignMessage.c,
result.optionalNestedMessage?.corecursive?.optionalForeignMessage?.c
Expand All @@ -60,4 +64,16 @@ class MessageTest {
val deser = messages.Messages.MessagesMessage.parseFrom(ProtoBuf.encodeToByteArray(result))
assertEquals(message, deser)
}

@Test
fun shouldSerNoField() {
val message = messages.MessageNoFieldsOuterClass.MessageNoFields.newBuilder().build()

val messageBytes = message.toByteArray()
val result: MessageNoFields = ProtoBuf.decodeFromByteArray(messageBytes)

val deser =
messages.MessageNoFieldsOuterClass.MessageNoFields.parseFrom(ProtoBuf.encodeToByteArray(result))
assertEquals(message, deser)
}
}
22 changes: 22 additions & 0 deletions testProtos/message_no_fields.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
syntax = "proto3";

package messages;

message MessageNoFields {
enum Type {
UNKNOWN = 0;
KNOWN = 1;
}
message SubMessageNoFields {
message SubMessageNoFieldsExtend {
Type type = 1;
}
optional SubMessageNoFields subHello = 1;
}

message SubMessageOneofFields {
oneof some_oneof {
int32 some_value = 1;
}
}
}