From bf080db0fea3d6eea240521c6a49c53771583b4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Quenaudon?= Date: Thu, 2 Jul 2026 14:32:57 +0100 Subject: [PATCH] FieldMask: runtime + schema awareness --- wire-runtime/api/wire-runtime.api | 12 +++++ .../kotlin/com/squareup/wire/FieldMask.kt | 37 ++++++++++++++ .../kotlin/com/squareup/wire/FieldMaskTest.kt | 48 +++++++++++++++++++ wire-schema/api/wire-schema.api | 1 + .../com/squareup/wire/schema/ProtoType.kt | 2 + .../com/squareup/wire/schema/ProtoTypeTest.kt | 6 +++ 6 files changed, 106 insertions(+) create mode 100644 wire-runtime/src/commonMain/kotlin/com/squareup/wire/FieldMask.kt create mode 100644 wire-runtime/src/commonTest/kotlin/com/squareup/wire/FieldMaskTest.kt diff --git a/wire-runtime/api/wire-runtime.api b/wire-runtime/api/wire-runtime.api index 4c164c15e8..43bd22d908 100644 --- a/wire-runtime/api/wire-runtime.api +++ b/wire-runtime/api/wire-runtime.api @@ -73,6 +73,18 @@ public final class com/squareup/wire/FieldEncoding : java/lang/Enum { public final class com/squareup/wire/FieldEncoding$Companion { } +public final class com/squareup/wire/FieldMask { + public fun ()V + public fun (Ljava/util/List;)V + public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun copy (Ljava/util/List;)Lcom/squareup/wire/FieldMask; + public static synthetic fun copy$default (Lcom/squareup/wire/FieldMask;Ljava/util/List;ILjava/lang/Object;)Lcom/squareup/wire/FieldMask; + public fun equals (Ljava/lang/Object;)Z + public final fun getPaths ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class com/squareup/wire/InstantKt { public static final fun ofEpochSecond (JJ)Ljava/time/Instant; } diff --git a/wire-runtime/src/commonMain/kotlin/com/squareup/wire/FieldMask.kt b/wire-runtime/src/commonMain/kotlin/com/squareup/wire/FieldMask.kt new file mode 100644 index 0000000000..e3e42c6c93 --- /dev/null +++ b/wire-runtime/src/commonMain/kotlin/com/squareup/wire/FieldMask.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2026 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.squareup.wire + +/** + * A set of symbolic field paths. + * + * Field masks are used to specify a subset of fields on a target message. Each path uses proto + * field names, separated by dots for nested fields. + */ +class FieldMask(paths: List = emptyList()) { + val paths: List = paths.toList() + + fun copy(paths: List = this.paths): FieldMask = FieldMask(paths) + + override fun equals(other: Any?): Boolean { + if (other === this) return true + return other is FieldMask && paths == other.paths + } + + override fun hashCode(): Int = paths.hashCode() + + override fun toString(): String = "FieldMask{paths=$paths}" +} diff --git a/wire-runtime/src/commonTest/kotlin/com/squareup/wire/FieldMaskTest.kt b/wire-runtime/src/commonTest/kotlin/com/squareup/wire/FieldMaskTest.kt new file mode 100644 index 0000000000..3f7474f82e --- /dev/null +++ b/wire-runtime/src/commonTest/kotlin/com/squareup/wire/FieldMaskTest.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2026 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.squareup.wire + +import assertk.assertThat +import assertk.assertions.containsExactly +import assertk.assertions.isEqualTo +import kotlin.test.Test + +class FieldMaskTest { + @Test fun storesPaths() { + val fieldMask = FieldMask(listOf("user.display_name", "photo")) + + assertThat(fieldMask.paths).containsExactly("user.display_name", "photo") + } + + @Test fun defaultPathsIsEmpty() { + assertThat(FieldMask().paths).isEqualTo(emptyList()) + } + + @Test fun pathsAreImmutableCopy() { + val paths = mutableListOf("user.display_name") + val fieldMask = FieldMask(paths) + + paths += "photo" + + assertThat(fieldMask.paths).containsExactly("user.display_name") + } + + @Test fun copy() { + val fieldMask = FieldMask(listOf("user.display_name")) + + assertThat(fieldMask.copy(paths = listOf("photo"))).isEqualTo(FieldMask(listOf("photo"))) + } +} diff --git a/wire-schema/api/wire-schema.api b/wire-schema/api/wire-schema.api index 4d4a9f2191..0069ab64d5 100644 --- a/wire-schema/api/wire-schema.api +++ b/wire-schema/api/wire-schema.api @@ -648,6 +648,7 @@ public final class com/squareup/wire/schema/ProtoType { public static final field DOUBLE_VALUE Lcom/squareup/wire/schema/ProtoType; public static final field DURATION Lcom/squareup/wire/schema/ProtoType; public static final field EMPTY Lcom/squareup/wire/schema/ProtoType; + public static final field FIELD_MASK Lcom/squareup/wire/schema/ProtoType; public static final field FIXED32 Lcom/squareup/wire/schema/ProtoType; public static final field FIXED64 Lcom/squareup/wire/schema/ProtoType; public static final field FLOAT Lcom/squareup/wire/schema/ProtoType; diff --git a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/ProtoType.kt b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/ProtoType.kt index 11ea414095..82bd090af6 100644 --- a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/ProtoType.kt +++ b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/ProtoType.kt @@ -138,6 +138,8 @@ class ProtoType { @JvmField val EMPTY = ProtoType(false, "google.protobuf.Empty") + @JvmField val FIELD_MASK = ProtoType(false, "google.protobuf.FieldMask") + @JvmField val STRUCT_MAP = ProtoType(false, "google.protobuf.Struct") @JvmField val STRUCT_VALUE = ProtoType(false, "google.protobuf.Value") diff --git a/wire-schema/src/commonTest/kotlin/com/squareup/wire/schema/ProtoTypeTest.kt b/wire-schema/src/commonTest/kotlin/com/squareup/wire/schema/ProtoTypeTest.kt index c1285173b5..b50f9b7c3d 100644 --- a/wire-schema/src/commonTest/kotlin/com/squareup/wire/schema/ProtoTypeTest.kt +++ b/wire-schema/src/commonTest/kotlin/com/squareup/wire/schema/ProtoTypeTest.kt @@ -117,6 +117,12 @@ class ProtoTypeTest { assertThat(phoneType.toString()).isEqualTo("squareup.protos.person.Person.PhoneType") } + @Test + fun fieldMask() { + assertThat(ProtoType.FIELD_MASK).isEqualTo(ProtoType.get("google.protobuf.FieldMask")) + assertThat(ProtoType.FIELD_MASK.typeUrl).isEqualTo("type.googleapis.com/google.protobuf.FieldMask") + } + @Test fun enclosingTypeOrPackage() { assertThat(ProtoType.STRING.enclosingTypeOrPackage).isNull()