Skip to content

Commit

Permalink
Introduce Opaquer
Browse files Browse the repository at this point in the history
  • Loading branch information
oldergod committed Oct 6, 2023
1 parent fc7e563 commit 06fbf30
Show file tree
Hide file tree
Showing 10 changed files with 357 additions and 10 deletions.
275 changes: 273 additions & 2 deletions wire-compiler/src/test/java/com/squareup/wire/schema/LinkerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,17 @@
package com.squareup.wire.schema

import com.squareup.wire.testing.add
import kotlin.test.assertFailsWith
import okio.Path
import okio.Path.Companion.toPath
import okio.fakefilesystem.FakeFileSystem
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test

class LinkerTest {
private val fs = FakeFileSystem().apply {
if (Path.DIRECTORY_SEPARATOR == "\\") emulateWindows() else emulateUnix()
createDirectories("proto-path".toPath())
}

@Test
Expand Down Expand Up @@ -56,6 +59,272 @@ class LinkerTest {
)
}

@Test fun opaqueMessageDeclaredField() {
fs.add(
"source-path/cafe/cafe.proto",
"""
|syntax = "proto2";
|
|package cafe;
|
|message CafeDrink {
| optional int32 size_ounces = 1;
| repeated EspressoShot shots = 2;
|}
|
|message EspressoShot {
| optional Roast roast = 1;
| optional bool decaf = 2;
|}
|
|enum Roast {
| MEDIUM = 1;
| DARK = 2;
|}
""".trimMargin(),
)
val schema = loadAndLinkSchema(opaqueTypes = listOf(ProtoType.get("cafe.EspressoShot")))
assertThat((schema.getType("cafe.CafeDrink") as MessageType).field("shots")!!.type!!)
.isEqualTo(ProtoType.BYTES)
assertThat(schema.protoFile("cafe/cafe.proto")!!.toSchema()).isEqualTo(
"""
|// Proto schema formatted by Wire, do not edit.
|// Source: cafe/cafe.proto
|
|syntax = "proto2";
|
|package cafe;
|
|message CafeDrink {
| optional int32 size_ounces = 1;
|
| repeated bytes shots = 2;
|}
|
|message EspressoShot {
| optional Roast roast = 1;
|
| optional bool decaf = 2;
|}
|
|enum Roast {
| MEDIUM = 1;
| DARK = 2;
|}
|
""".trimMargin(),
)
}

@Test fun opaqueEnumDeclaredField() {
fs.add(
"source-path/cafe/cafe.proto",
"""
|syntax = "proto2";
|
|package cafe;
|
|message CafeDrink {
| optional int32 size_ounces = 1;
| repeated EspressoShot shots = 2;
|}
|
|message EspressoShot {
| optional Roast roast = 1;
| optional bool decaf = 2;
|}
|
|enum Roast {
| MEDIUM = 1;
| DARK = 2;
|}
""".trimMargin(),
)
val schema = loadAndLinkSchema(opaqueTypes = listOf(ProtoType.get("cafe.Roast")))
assertThat((schema.getType("cafe.EspressoShot") as MessageType).field("roast")!!.type!!)
.isEqualTo(ProtoType.BYTES)
assertThat(schema.protoFile("cafe/cafe.proto")!!.toSchema()).isEqualTo(
"""
|// Proto schema formatted by Wire, do not edit.
|// Source: cafe/cafe.proto
|
|syntax = "proto2";
|
|package cafe;
|
|message CafeDrink {
| optional int32 size_ounces = 1;
|
| repeated EspressoShot shots = 2;
|}
|
|message EspressoShot {
| optional bytes roast = 1;
|
| optional bool decaf = 2;
|}
|
|enum Roast {
| MEDIUM = 1;
| DARK = 2;
|}
|
""".trimMargin(),
)
}

@Test fun opaqueExtensionField() {
fs.add(
"source-path/cafe/cafe.proto",
"""
|syntax = "proto2";
|
|package cafe;
|
|message CafeDrink {
| optional int32 size_ounces = 1;
|}
|
|extend CafeDrink {
| repeated EspressoShot shots = 2;
|}
|
|message EspressoShot {
| optional Roast roast = 1;
| optional bool decaf = 2;
|}
|
|enum Roast {
| MEDIUM = 1;
| DARK = 2;
|}
""".trimMargin(),
)
val schema = loadAndLinkSchema(opaqueTypes = listOf(ProtoType.get("cafe.EspressoShot")))
assertThat((schema.getType("cafe.CafeDrink") as MessageType).extensionField("cafe.shots")!!.type!!)
.isEqualTo(ProtoType.BYTES)
assertThat(schema.protoFile("cafe/cafe.proto")!!.toSchema()).isEqualTo(
"""
|// Proto schema formatted by Wire, do not edit.
|// Source: cafe/cafe.proto
|
|syntax = "proto2";
|
|package cafe;
|
|message CafeDrink {
| optional int32 size_ounces = 1;
|}
|
|message EspressoShot {
| optional Roast roast = 1;
|
| optional bool decaf = 2;
|}
|
|enum Roast {
| MEDIUM = 1;
| DARK = 2;
|}
|
|extend CafeDrink {
| repeated bytes shots = 2;
|}
|
""".trimMargin(),
)
}

@Test fun opaqueMultipleFields() {
fs.add(
"source-path/cafe/cafe.proto",
"""
|syntax = "proto2";
|
|package cafe;
|
|message CafeDrink {
| optional int32 size_ounces = 1;
| repeated EspressoShot shots = 2;
|}
|
|message EspressoShot {
| optional Roast roast = 1;
| optional bool decaf = 2;
|}
|
|enum Roast {
| MEDIUM = 1;
| DARK = 2;
|}
""".trimMargin(),
)
val schema = loadAndLinkSchema(
opaqueTypes = listOf(
ProtoType.get("cafe.EspressoShot"),
ProtoType.get("cafe.Roast"),
),
)
assertThat((schema.getType("cafe.CafeDrink") as MessageType).field("shots")!!.type!!)
.isEqualTo(ProtoType.BYTES)
assertThat((schema.getType("cafe.EspressoShot") as MessageType).field("roast")!!.type!!)
.isEqualTo(ProtoType.BYTES)
assertThat(schema.protoFile("cafe/cafe.proto")!!.toSchema()).isEqualTo(
"""
|// Proto schema formatted by Wire, do not edit.
|// Source: cafe/cafe.proto
|
|syntax = "proto2";
|
|package cafe;
|
|message CafeDrink {
| optional int32 size_ounces = 1;
|
| repeated bytes shots = 2;
|}
|
|message EspressoShot {
| optional bytes roast = 1;
|
| optional bool decaf = 2;
|}
|
|enum Roast {
| MEDIUM = 1;
| DARK = 2;
|}
|
""".trimMargin(),
)
}

@Test fun opaqueScalarTypeThrows() {
fs.add(
"source-path/cafe/cafe.proto",
"""
|syntax = "proto2";
|
|package cafe;
|
|message CafeDrink {
| optional int32 size_ounces = 1;
|}
""".trimMargin(),
)

val exception = assertFailsWith<SchemaException> {
loadAndLinkSchema(opaqueTypes = listOf(ProtoType.INT32))
}
assertThat(exception).hasMessageContaining(
"""
|Scalar types like int32 cannot be opaqued
| for field size_ounces (source-path/cafe/cafe.proto:6:3)
| in message cafe.CafeDrink (source-path/cafe/cafe.proto:5:1)
""".trimMargin(),
)
}

@Test
fun unusedProtoPathFileExcludedFromSchema() {
fs.add(
Expand Down Expand Up @@ -225,15 +494,17 @@ class LinkerTest {
|}
""".trimMargin(),
)
fs.add("proto-path/b.proto", "")
val schema = loadAndLinkSchema()

val enumValueDeprecated = schema.getField(Options.ENUM_VALUE_OPTIONS, "deprecated")
assertThat(enumValueDeprecated!!.encodeMode).isNotNull()
}

private fun loadAndLinkSchema(): Schema {
private fun loadAndLinkSchema(
opaqueTypes: List<ProtoType> = listOf(),
): Schema {
val loader = SchemaLoader(fs)
loader.opaqueTypes = opaqueTypes
loader.initRoots(
sourcePath = listOf(Location.get("source-path")),
protoPath = listOf(Location.get("proto-path")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,32 @@ class WireRunTest {
.contains("public final class Red extends Message")
}

@Test
fun opaqueBeforeGeneratingKtThenJava() {
writeBlueProto()
writeTriangleProto()

val wireRun = WireRun(
sourcePath = listOf(Location.get("colors/src/main/proto")),
protoPath = listOf(Location.get("polygons/src/main/proto")),
opaqueTypes = listOf("squareup.polygons.Triangle"),
targets = listOf(
KotlinTarget(
outDirectory = "generated/kt",
),
),
)
wireRun.execute(fs, logger)

assertThat(fs.findFiles("generated")).containsExactlyInAnyOrderAsRelativePaths(
"generated/kt/squareup/colors/Blue.kt",
)
assertThat(fs.readUtf8("generated/kt/squareup/colors/Blue.kt"))
.contains("class Blue")
// The type `Triangle` has been opaqued.
.contains("public val triangle: ByteString? = null")
}

@Test
fun noSuchClassEventListener() {
assertThat(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ data class Field(

val default: String?,

private val elementType: String,
private var elementType: String,

val options: Options,

Expand Down Expand Up @@ -122,6 +122,10 @@ data class Field(

fun link(linker: Linker) {
type = linker.withContext(this).resolveType(elementType)
if (type == ProtoType.BYTES && elementType != "bytes") {
// The type has been opaqued, we update its proto definition as well.
elementType = "bytes"
}
}

fun linkOptions(linker: Linker, syntaxRules: SyntaxRules, validate: Boolean) {
Expand Down
Loading

0 comments on commit 06fbf30

Please sign in to comment.