diff --git a/wire-compiler/src/test/java/com/squareup/wire/schema/LinkerTest.kt b/wire-compiler/src/test/java/com/squareup/wire/schema/LinkerTest.kt index 13013501f5..a561d6706f 100644 --- a/wire-compiler/src/test/java/com/squareup/wire/schema/LinkerTest.kt +++ b/wire-compiler/src/test/java/com/squareup/wire/schema/LinkerTest.kt @@ -18,7 +18,9 @@ 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 @@ -26,6 +28,7 @@ import org.junit.Test class LinkerTest { private val fs = FakeFileSystem().apply { if (Path.DIRECTORY_SEPARATOR == "\\") emulateWindows() else emulateUnix() + createDirectories("proto-path".toPath()) } @Test @@ -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 { + 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( @@ -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 = listOf(), + ): Schema { val loader = SchemaLoader(fs) + loader.opaqueTypes = opaqueTypes loader.initRoots( sourcePath = listOf(Location.get("source-path")), protoPath = listOf(Location.get("proto-path")), diff --git a/wire-compiler/src/test/java/com/squareup/wire/schema/WireRunTest.kt b/wire-compiler/src/test/java/com/squareup/wire/schema/WireRunTest.kt index 800163e292..3aa59dbcac 100644 --- a/wire-compiler/src/test/java/com/squareup/wire/schema/WireRunTest.kt +++ b/wire-compiler/src/test/java/com/squareup/wire/schema/WireRunTest.kt @@ -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( diff --git a/wire-schema-tests/api/wire-schema-tests.api b/wire-schema-tests/api/wire-schema-tests.api index a2a6e49975..168d378011 100644 --- a/wire-schema-tests/api/wire-schema-tests.api +++ b/wire-schema-tests/api/wire-schema-tests.api @@ -2,6 +2,7 @@ public final class com/squareup/wire/SchemaBuilder { public fun ()V public final fun add (Lokio/Path;Ljava/lang/String;)Lcom/squareup/wire/SchemaBuilder; public final fun add (Lokio/Path;Ljava/lang/String;Lokio/Path;)Lcom/squareup/wire/SchemaBuilder; + public final fun addOpaqueTypes ([Lcom/squareup/wire/schema/ProtoType;)Lcom/squareup/wire/SchemaBuilder; public final fun addProtoPath (Lokio/Path;Ljava/lang/String;)Lcom/squareup/wire/SchemaBuilder; public final fun build ()Lcom/squareup/wire/schema/Schema; } diff --git a/wire-schema-tests/src/commonMain/kotlin/com/squareup/wire/SchemaBuilder.kt b/wire-schema-tests/src/commonMain/kotlin/com/squareup/wire/SchemaBuilder.kt index 84fe5f3de7..7e194ba482 100644 --- a/wire-schema-tests/src/commonMain/kotlin/com/squareup/wire/SchemaBuilder.kt +++ b/wire-schema-tests/src/commonMain/kotlin/com/squareup/wire/SchemaBuilder.kt @@ -16,6 +16,7 @@ package com.squareup.wire import com.squareup.wire.schema.Location +import com.squareup.wire.schema.ProtoType import com.squareup.wire.schema.Schema import com.squareup.wire.schema.SchemaLoader import okio.FileSystem @@ -29,6 +30,7 @@ class SchemaBuilder { private val sourcePath: Path = "/sourcePath".toPath() private val protoPath: Path = "/protoPath".toPath() private val fileSystem: FileSystem = FakeFileSystem() + private var opaqueTypes = mutableListOf() init { fileSystem.createDirectories(sourcePath) @@ -77,8 +79,15 @@ class SchemaBuilder { return add(name, protoFile, protoPath) } + /** See [SchemaLoader.opaqueTypes]. */ + fun addOpaqueTypes(vararg opaqueTypes: ProtoType): SchemaBuilder { + this.opaqueTypes.addAll(opaqueTypes) + return this + } + fun build(): Schema { val schemaLoader = SchemaLoader(fileSystem) + schemaLoader.opaqueTypes = opaqueTypes.toList() schemaLoader.initRoots( sourcePath = listOf(Location.get(sourcePath.toString())), protoPath = listOf(Location.get(protoPath.toString())), diff --git a/wire-schema/api/wire-schema.api b/wire-schema/api/wire-schema.api index a19b53b261..e21eb2c376 100644 --- a/wire-schema/api/wire-schema.api +++ b/wire-schema/api/wire-schema.api @@ -370,6 +370,7 @@ public final class com/squareup/wire/schema/LinkedOptionEntry { public final class com/squareup/wire/schema/Linker { public fun (Lcom/squareup/wire/schema/Loader;Lcom/squareup/wire/schema/ErrorCollector;ZZ)V + public fun (Lcom/squareup/wire/schema/Loader;Lcom/squareup/wire/schema/ErrorCollector;ZZLjava/util/List;)V public final fun addType (Lcom/squareup/wire/schema/ProtoType;Lcom/squareup/wire/schema/Type;)V public final fun dereference (Lcom/squareup/wire/schema/ProtoType;Ljava/lang/String;)Lcom/squareup/wire/schema/Field; public final fun get (Lcom/squareup/wire/schema/ProtoType;)Lcom/squareup/wire/schema/Type; @@ -883,6 +884,7 @@ public final class com/squareup/wire/schema/SchemaLoader : com/squareup/wire/sch public fun (Ljava/nio/file/FileSystem;)V public fun (Lokio/FileSystem;)V public final fun getLoadExhaustively ()Z + public final fun getOpaqueTypes ()Ljava/util/List; public final fun getPermitPackageCycles ()Z public final fun getSourcePathFiles ()Ljava/util/List; public final fun initRoots (Ljava/util/List;Ljava/util/List;)V @@ -891,6 +893,7 @@ public final class com/squareup/wire/schema/SchemaLoader : com/squareup/wire/sch public fun loadProfile (Ljava/lang/String;Lcom/squareup/wire/schema/Schema;)Lcom/squareup/wire/schema/Profile; public final fun loadSchema ()Lcom/squareup/wire/schema/Schema; public final fun setLoadExhaustively (Z)V + public final fun setOpaqueTypes (Ljava/util/List;)V public final fun setPermitPackageCycles (Z)V public synthetic fun withErrors (Lcom/squareup/wire/schema/ErrorCollector;)Lcom/squareup/wire/schema/Loader; public fun withErrors (Lcom/squareup/wire/schema/ErrorCollector;)Lcom/squareup/wire/schema/SchemaLoader; @@ -985,13 +988,14 @@ public final class com/squareup/wire/schema/WireLoggersKt { } public final class com/squareup/wire/schema/WireRun { - public fun (Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;ZLjava/util/List;Z)V - public synthetic fun (Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;ZLjava/util/List;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;ZLjava/util/List;ZLjava/util/List;)V + public synthetic fun (Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;ZLjava/util/List;ZLjava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun execute (Lokio/FileSystem;Lcom/squareup/wire/WireLogger;)V public final fun getEventListeners ()Ljava/util/List; public final fun getModules ()Ljava/util/Map; public final fun getMoves ()Ljava/util/List; public final fun getOnlyVersion ()Ljava/lang/String; + public final fun getOpaqueTypes ()Ljava/util/List; public final fun getPermitPackageCycles ()Z public final fun getProtoPath ()Ljava/util/List; public final fun getRejectUnusedRootsOrPrunes ()Z diff --git a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/Field.kt b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/Field.kt index 85d8a2456e..75b36370ff 100644 --- a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/Field.kt +++ b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/Field.kt @@ -45,7 +45,7 @@ data class Field( val default: String?, - private val elementType: String, + private var elementType: String, val options: Options, @@ -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) { diff --git a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/Linker.kt b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/Linker.kt index 1b42ea8c8c..4636ac80cd 100644 --- a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/Linker.kt +++ b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/Linker.kt @@ -15,6 +15,7 @@ */ package com.squareup.wire.schema +import com.squareup.wire.schema.ProtoType.Companion.BYTES import com.squareup.wire.schema.ProtoType.Companion.get import com.squareup.wire.schema.internal.MutableQueue import com.squareup.wire.schema.internal.isValidTag @@ -30,6 +31,7 @@ class Linker { private val requestedTypes: MutableSet private val requestedFields: MutableSet private val permitPackageCycles: Boolean + private val opaqueTypes: List val loadExhaustively: Boolean /** Errors accumulated by this load. */ @@ -40,6 +42,7 @@ class Linker { errors: ErrorCollector, permitPackageCycles: Boolean, loadExhaustively: Boolean, + opaqueTypes: List, ) { this.loader = loader this.fileLinkers = mutableMapOf() @@ -51,8 +54,22 @@ class Linker { this.errors = errors this.permitPackageCycles = permitPackageCycles this.loadExhaustively = loadExhaustively + this.opaqueTypes = opaqueTypes } + constructor( + loader: Loader, + errors: ErrorCollector, + permitPackageCycles: Boolean, + loadExhaustively: Boolean, + ) : this( + loader = loader, + errors = errors, + permitPackageCycles = permitPackageCycles, + loadExhaustively = loadExhaustively, + opaqueTypes = listOf(), + ) + private constructor( enclosing: Linker, additionalContext: Any, @@ -67,6 +84,7 @@ class Linker { this.errors = enclosing.errors.at(additionalContext) this.permitPackageCycles = false this.loadExhaustively = enclosing.loadExhaustively + this.opaqueTypes = enclosing.opaqueTypes } /** Returns a linker for [path], loading the file if necessary. */ @@ -209,6 +227,9 @@ class Linker { if (messageOnly) { errors += "expected a message but was $name" } + if (type in opaqueTypes) { + errors += "Scalar types like $type cannot be opaqued" + } return type } @@ -232,17 +253,20 @@ class Linker { if (resolved == null) { errors += "unable to resolve $name" - return ProtoType.BYTES // Just return any placeholder. + return BYTES // Just return any placeholder. } if (messageOnly && resolved !is MessageType) { errors += "expected a message but was $name" - return ProtoType.BYTES // Just return any placeholder. + return BYTES // Just return any placeholder. } - requestedTypes.add(resolved.type) - - return resolved.type + if (resolved.type in opaqueTypes) { + return BYTES + } else { + requestedTypes.add(resolved.type) + return resolved.type + } } fun resolve( @@ -492,7 +516,6 @@ class Linker { } } - @Suppress("MoveLambdaOutsideParentheses") fun validateEnumConstantNameUniqueness(nestedTypes: Iterable) { val nameToType = mutableMapOf>() for (type in nestedTypes) { diff --git a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/SchemaLoader.kt b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/SchemaLoader.kt index 140160ab95..3fe5fd49b9 100644 --- a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/SchemaLoader.kt +++ b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/SchemaLoader.kt @@ -26,6 +26,13 @@ expect class SchemaLoader(fileSystem: FileSystem) : Loader, ProfileLoader { /** Strict by default. Note that golang cannot build protos with package cycles. */ var permitPackageCycles: Boolean + /** + * All qualified named Protobuf types in [opaqueTypes] will be evaluated as being of type `bytes`. + * On code generation, the fields of such types will be using the platform equivalent of `bytes`, + * like [okio.ByteString] for the JVM. Note that scalar types cannot be opaqued. + */ + var opaqueTypes: List + /** * If true, the schema loader will load the whole graph, including files and types not used by * anything in the source path. diff --git a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/WireRun.kt b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/WireRun.kt index 31f2148952..3317e59d49 100644 --- a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/WireRun.kt +++ b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/WireRun.kt @@ -194,6 +194,14 @@ class WireRun( * If false, unused [treeShakingRoots] and [treeShakingRubbish] will be printed as warnings. */ val rejectUnusedRootsOrPrunes: Boolean = false, + + /** + * All qualified named Protobuf types in [opaqueTypes] will be evaluated as being of type `bytes`. + * On code generation, the fields of such types will be using the platform equivalent of `bytes`, + * like [okio.ByteString] for the JVM. Note that scalar types cannot be opaqued. + * The opaque step will happen before the tree shaking one. + */ + val opaqueTypes: List = listOf(), ) { data class Module( val dependencies: Set = emptySet(), @@ -227,6 +235,7 @@ class WireRun( checkForModuleCycles() schemaLoader.permitPackageCycles = permitPackageCycles + schemaLoader.opaqueTypes = opaqueTypes.map(ProtoType::get) schemaLoader.initRoots(sourcePath, protoPath) // Validate the schema and resolve references diff --git a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/CommonSchemaLoader.kt b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/CommonSchemaLoader.kt index db04749dfc..e378a4a9da 100644 --- a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/CommonSchemaLoader.kt +++ b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/internal/CommonSchemaLoader.kt @@ -53,6 +53,8 @@ internal class CommonSchemaLoader : Loader, ProfileLoader { /** Strict by default. Note that golang cannot build protos with package cycles. */ var permitPackageCycles = false + var opaqueTypes: List = listOf() + /** * If true, the schema loader will load the whole graph, including files and types not used by * anything in the source path. @@ -99,7 +101,7 @@ internal class CommonSchemaLoader : Loader, ProfileLoader { @Throws(IOException::class) fun loadSchema(): Schema { sourcePathFiles = loadSourcePathFiles() - val linker = Linker(this, errors, permitPackageCycles, loadExhaustively) + val linker = Linker(this, errors, permitPackageCycles, loadExhaustively, opaqueTypes) val result = linker.link(sourcePathFiles) errors.throwIfNonEmpty() return result diff --git a/wire-schema/src/jsMain/kotlin/com/squareup/wire/schema/SchemaLoader.kt b/wire-schema/src/jsMain/kotlin/com/squareup/wire/schema/SchemaLoader.kt index 4cb4046dc6..a64a304038 100644 --- a/wire-schema/src/jsMain/kotlin/com/squareup/wire/schema/SchemaLoader.kt +++ b/wire-schema/src/jsMain/kotlin/com/squareup/wire/schema/SchemaLoader.kt @@ -38,6 +38,12 @@ actual class SchemaLoader : Loader, ProfileLoader { delegate.permitPackageCycles = value } + actual var opaqueTypes: List + get() = delegate.opaqueTypes + set(value) { + delegate.opaqueTypes = value + } + /** * If true, the schema loader will load the whole graph, including files and types not used by * anything in the source path. diff --git a/wire-schema/src/jvmMain/kotlin/com/squareup/wire/schema/SchemaLoader.kt b/wire-schema/src/jvmMain/kotlin/com/squareup/wire/schema/SchemaLoader.kt index 186dab500c..0968070152 100644 --- a/wire-schema/src/jvmMain/kotlin/com/squareup/wire/schema/SchemaLoader.kt +++ b/wire-schema/src/jvmMain/kotlin/com/squareup/wire/schema/SchemaLoader.kt @@ -42,6 +42,12 @@ actual class SchemaLoader : Loader, ProfileLoader { delegate.permitPackageCycles = value } + actual var opaqueTypes: List + get() = delegate.opaqueTypes + set(value) { + delegate.opaqueTypes = value + } + /** * If true, the schema loader will load the whole graph, including files and types not used by * anything in the source path. diff --git a/wire-schema/src/jvmTest/kotlin/com/squareup/wire/schema/PrunerTest.kt b/wire-schema/src/jvmTest/kotlin/com/squareup/wire/schema/PrunerTest.kt index b754b472b7..4a9bb8d94d 100644 --- a/wire-schema/src/jvmTest/kotlin/com/squareup/wire/schema/PrunerTest.kt +++ b/wire-schema/src/jvmTest/kotlin/com/squareup/wire/schema/PrunerTest.kt @@ -56,6 +56,51 @@ class PrunerTest { assertThat(pruned.getType("MessageB")).isNull() } + /** + * We test that all references of an opaque type are getting pruned correctly. + */ + @Test + fun opaqueTypePrunesItsReferences() { + val schema = buildSchema { + add( + "source-path/cafe/cafe.proto".toPath(), + """ + |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(), + ) + addOpaqueTypes(ProtoType.get("cafe.EspressoShot")) + } + val pruned = schema.prune( + PruningRules.Builder() + .addRoot("cafe.CafeDrink") + .build(), + ) + assertThat(pruned.getType("cafe.CafeDrink")).isNotNull() + // The field should not be pruned, and is of the opaque type `bytes`. + assertThat(pruned.getField("cafe.CafeDrink", "shots")!!.type) + .isEqualTo(ProtoType.BYTES) + // Types which were originally referred by `shots` are now pruned since the field is opaqued. + assertThat(pruned.getType("cafe.EspressoShot")).isNull() + assertThat(pruned.getType("cafe.Roast")).isNull() + } + @Test fun retainMap() { val schema = buildSchema { diff --git a/wire-schema/src/nativeMain/kotlin/com/squareup/wire/schema/SchemaLoader.kt b/wire-schema/src/nativeMain/kotlin/com/squareup/wire/schema/SchemaLoader.kt index 4cb4046dc6..a64a304038 100644 --- a/wire-schema/src/nativeMain/kotlin/com/squareup/wire/schema/SchemaLoader.kt +++ b/wire-schema/src/nativeMain/kotlin/com/squareup/wire/schema/SchemaLoader.kt @@ -38,6 +38,12 @@ actual class SchemaLoader : Loader, ProfileLoader { delegate.permitPackageCycles = value } + actual var opaqueTypes: List + get() = delegate.opaqueTypes + set(value) { + delegate.opaqueTypes = value + } + /** * If true, the schema loader will load the whole graph, including files and types not used by * anything in the source path.